<!DOCTYPE html>


  <html class="dark page-post">


<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>Taro原理总结 | Poetry&#39;s Blog</title>

  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  
    <meta name="keywords" content="Taro,">
  

  <meta name="description" content="来自掘金小册笔记  一、Taro 的安装与使用1.1 安装$ npm install -g @tarojs/cli taro -V 1.2 使用使用命令创建模板项目 $ taro init myApp  1.2.1 微信小程序 选择微信小程序模式，需要自行下载并打开微信开发者工具，然后选择项目根目录进行预览  微信小程序编译预览及打包 # npm script$ npm run dev:weap">
<meta name="keywords" content="Taro">
<meta property="og:type" content="article">
<meta property="og:title" content="Taro原理总结">
<meta property="og:url" content="http://blog.poetries.top/2018/11/26/taro-theory/index.html">
<meta property="og:site_name" content="Poetry&#39;s Blog">
<meta property="og:description" content="来自掘金小册笔记  一、Taro 的安装与使用1.1 安装$ npm install -g @tarojs/cli taro -V 1.2 使用使用命令创建模板项目 $ taro init myApp  1.2.1 微信小程序 选择微信小程序模式，需要自行下载并打开微信开发者工具，然后选择项目根目录进行预览  微信小程序编译预览及打包 # npm script$ npm run dev:weap">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/9/2/1659a045be8713ca">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/7/1664ea0616676b32">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/7/1664ea05f7c23984">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/7/1664ea06098d0a4c">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/7/1664ea0608d0d42e">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/1665182480dfc020">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/1665182486f397d9">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/1665182487386fef">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/16651824884a5682">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/16651824b8ac59a4">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/16651547b6ddebe1">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515483b7fa7c0">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/1665155932b630fe">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/1665157669296bc1">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/1665157cb5a81196">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515c132121443">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515c132336584">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12e8fe10">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12d7da84">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12e9969d">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12c9ef95">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12fc3d2c">
<meta property="og:updated_time" content="2020-08-15T04:25:31.938Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Taro原理总结">
<meta name="twitter:description" content="来自掘金小册笔记  一、Taro 的安装与使用1.1 安装$ npm install -g @tarojs/cli taro -V 1.2 使用使用命令创建模板项目 $ taro init myApp  1.2.1 微信小程序 选择微信小程序模式，需要自行下载并打开微信开发者工具，然后选择项目根目录进行预览  微信小程序编译预览及打包 # npm script$ npm run dev:weap">
<meta name="twitter:image" content="https://user-gold-cdn.xitu.io/2018/9/2/1659a045be8713ca">

  

  
    <link rel="icon" href="/favicon.ico">
  

  <link href="/css/styles.css?v=c114cbeddx" rel="stylesheet">
<link href="/css/other.css?v=c114cbeddx" rel="stylesheet">


  
    <link rel="stylesheet" href="/css/personal-style.css">
  

  

  
  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "//hm.baidu.com/hm.js?40b1f89aa80f2527b3db779c6898c879";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>


  
  <script type="text/javascript">
	(function(){
	    var bp = document.createElement('script');
	    var curProtocol = window.location.protocol.split(':')[0];
	    if (curProtocol === 'https') {
	        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';        
	    }
	    else {
	        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
	    }
	    var s = document.getElementsByTagName("script")[0];
	    s.parentNode.insertBefore(bp, s);
	})();
  </script>



  
    <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <link rel="stylesheet" href="//cdn.bootcss.com/font-awesome/4.3.0/css/font-awesome.min.css">
  

  <!-- 聊天系统 -->
  
    
   <link type="text/css" rel="stylesheet" href="/renxi/default.css">
   <style>
      #modal {
        position: static !important;
      }
      .filter {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        background: #fe5757;
        animation: colorChange 30s ease-in-out infinite;
        animation-fill-mode: both;
        mix-blend-mode: overlay;
      }
  
      @keyframes colorChange {
        0%, 100% {
            opacity: 0;
        }
        50% {
            opacity: .9;
        }
      }
   </style>
</head>
</html>
<body>
  
  
    <span id="toolbox-mobile" class="toolbox-mobile">导航</span>
  

  <div class="post-header CENTER">
   
  <div class="toolbox">
    <a class="toolbox-entry" href="/">
      <span class="toolbox-entry-text">导航</span>
      <i class="icon-angle-down"></i>
      <i class="icon-home"></i>
    </a>
    <ul class="list-toolbox">
      
        <li class="item-toolbox">
          <a
            class="CIRCLE"
            href="/archives/"
            rel="noopener noreferrer"
            target="_self"
            >
            博客
          </a>
        </li>
      
        <li class="item-toolbox">
          <a
            class="CIRCLE"
            href="/categories/"
            rel="noopener noreferrer"
            target="_self"
            >
            分类
          </a>
        </li>
      
        <li class="item-toolbox">
          <a
            class="CIRCLE"
            href="/tags/"
            rel="noopener noreferrer"
            target="_self"
            >
            标签
          </a>
        </li>
      
        <li class="item-toolbox">
          <a
            class="CIRCLE"
            href="/search/"
            rel="noopener noreferrer"
            target="_self"
            >
            搜索
          </a>
        </li>
      
        <li class="item-toolbox">
          <a
            class="CIRCLE"
            href="/link/"
            rel="noopener noreferrer"
            target="_self"
            >
            友链
          </a>
        </li>
      
        <li class="item-toolbox">
          <a
            class="CIRCLE"
            href="/about/"
            rel="noopener noreferrer"
            target="_self"
            >
            关于
          </a>
        </li>
      
    </ul>
  </div>


</div>


  <div id="toc" class="toc-article">
    <strong class="toc-title">文章目录<i class="iconfont toc-title" style="display:inline-block;color:#87998d;width:20px;height:20px;">&#xf004b;</i></strong>
    <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#一、Taro-的安装与使用"><span class="toc-text">一、Taro 的安装与使用</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-1-安装"><span class="toc-text">1.1 安装</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-2-使用"><span class="toc-text">1.2 使用</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-2-1-微信小程序"><span class="toc-text">1.2.1 微信小程序</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#1-2-2-百度小程序"><span class="toc-text">1.2.2 百度小程序</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#1-2-3-支付宝小程序"><span class="toc-text">1.2.3 支付宝小程序</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#1-2-4-H5"><span class="toc-text">1.2.4 H5</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#1-2-5-React-Native"><span class="toc-text">1.2.5 React Native</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-3-更新-Taro"><span class="toc-text">1.3 更新 Taro</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#二、Taro-开发说明与注意事项"><span class="toc-text">二、Taro 开发说明与注意事项</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-微信小程序开发工具的配置"><span class="toc-text">2.1 微信小程序开发工具的配置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-Taro-与-React-的差异"><span class="toc-text">2.2 Taro 与 React 的差异</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-1-暂不支持在-render-之外的方法定义-JSX"><span class="toc-text">2.2.1 暂不支持在 render() 之外的方法定义 JSX</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-2-不能在包含-JSX-元素的-map-循环中使用-if-表达式"><span class="toc-text">2.2.2 不能在包含 JSX 元素的 map 循环中使用 if 表达式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-3-不能使用-Array-map-之外的方法操作-JSX-数组"><span class="toc-text">2.2.3 不能使用 Array.map 之外的方法操作 JSX 数组</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-4-不能在-JSX-参数中使用匿名函数"><span class="toc-text">2.2.4 不能在 JSX 参数中使用匿名函数</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-5-不能在-JSX-参数中使用对象展开符"><span class="toc-text">2.2.5 不能在 JSX 参数中使用对象展开符</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-6-不允许在-JSX-参数（props）中传入-JSX-元素"><span class="toc-text">2.2.6 不允许在 JSX 参数（props）中传入 JSX 元素</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-7-不支持无状态组件（Stateless-Component"><span class="toc-text">2.2.7 不支持无状态组件（Stateless Component)</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3-命名规范"><span class="toc-text">2.3 命名规范</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-4-推荐安装-ESLint-编辑器插件"><span class="toc-text">2.4 推荐安装 ESLint 编辑器插件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-5-最佳编码方式"><span class="toc-text">2.5 最佳编码方式</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#三、Taro-设计思想及架构"><span class="toc-text">三、Taro 设计思想及架构</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-抹平多端差异"><span class="toc-text">3.1 抹平多端差异</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#四、CLI-原理及不同端的运行机制"><span class="toc-text">四、CLI 原理及不同端的运行机制</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#4-1-taro-cli-包"><span class="toc-text">4.1 taro-cli 包</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#4-1-1-Taro-命令"><span class="toc-text">4.1.1 Taro 命令</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-1-2-包管理与发布"><span class="toc-text">4.1.2 包管理与发布</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-1-3-taro-cli-包的目录结构如下"><span class="toc-text">4.1.3 taro-cli 包的目录结构如下</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-2-用到的核心库"><span class="toc-text">4.2 用到的核心库</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-3-Taro-Init"><span class="toc-text">4.3 Taro Init</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#4-3-1-命令关联与参数解析"><span class="toc-text">4.3.1 命令关联与参数解析</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-3-2-参数解析及与用户交互"><span class="toc-text">4.3.2 参数解析及与用户交互</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-3-3-模版文件操作"><span class="toc-text">4.3.3 模版文件操作</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-4-Taro-Build"><span class="toc-text">4.4 Taro Build</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#4-4-1-编译工作流与抽象语法树（AST）"><span class="toc-text">4.4.1 编译工作流与抽象语法树（AST）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-4-2-Babel-模块"><span class="toc-text">4.4.2 Babel 模块</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-4-3-解析页面-Config-配置"><span class="toc-text">4.4.3 解析页面 Config 配置</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#五、Taro-组件库及-API-的设计与适配"><span class="toc-text">五、Taro 组件库及 API 的设计与适配</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-多端差异"><span class="toc-text">5.1 多端差异</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#5-1-1-组件差异"><span class="toc-text">5.1.1 组件差异</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-1-2-API-差异"><span class="toc-text">5.1.2 API 差异</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-多端适配"><span class="toc-text">5.2 多端适配</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#5-2-1-样式处理"><span class="toc-text">5.2.1 样式处理</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-2-2-组件封装"><span class="toc-text">5.2.2 组件封装</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#六、JSX-转换微信小程序模板的实现"><span class="toc-text">六、JSX 转换微信小程序模板的实现</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#6-1-代码的本质"><span class="toc-text">6.1 代码的本质</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-2-Babel"><span class="toc-text">6.2 Babel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-3-实践例子"><span class="toc-text">6.3 实践例子</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#6-3-1-设计思路"><span class="toc-text">6.3.1 设计思路</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-3-2-WXML-和-JSX"><span class="toc-text">6.3.2 WXML 和 JSX</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#七、小程序运行时"><span class="toc-text">七、小程序运行时</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1-注册程序、页面以及自定义组件"><span class="toc-text">7.1 注册程序、页面以及自定义组件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-2-组件-state-转换"><span class="toc-text">7.2 组件 state 转换</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-3-将组件的生命周期对应到小程序组件的生命周期"><span class="toc-text">7.3 将组件的生命周期对应到小程序组件的生命周期</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-4-事件处理函数对应"><span class="toc-text">7.4 事件处理函数对应</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-5-对-API-进行-Promise-化的处理"><span class="toc-text">7.5 对 API 进行 Promise 化的处理</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#八、H5-运行时"><span class="toc-text">八、H5 运行时</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#8-1-H5-运行时解析"><span class="toc-text">8.1 H5 运行时解析</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#8-1-1-组件实现"><span class="toc-text">8.1.1 组件实现</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-2-API-适配"><span class="toc-text">8.2 API 适配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-3-路由"><span class="toc-text">8.3 路由</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-4-Redux-处理"><span class="toc-text">8.4 Redux 处理</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#九、更多参考"><span class="toc-text">九、更多参考</span></a></li></ol>
  </div>
  




<div class="content content-post CENTER">
   <!-- canvas 彩带 -->
<canvas id="evanyou" width="1302" height="678" style="position: fixed;width: 100%;height: 100%;top: 0;left:0;z-index:-1;"></canvas>

<article id="post-taro-theory" class="article article-type-post" itemprop="blogPost">
  <header class="article-header" style="position:relative;">
    <h1 class="post-title">Taro原理总结</h1>

    <div class="article-meta">
      <span>
        <i class="icon-calendar"></i>
        <span>2018.11.26</span>
      </span>

      
        <span class="article-author">
          <i class="icon-user"></i>
          <span>Poetry</span>
        </span>
      

      
  <span class="article-category">
    <i class="icon-list"></i>
    <a class="article-category-link" href="/categories/Front-End/">Front-End</a>
  </span>



      

      
      <i class="fa fa-eye"></i> 
        <span id="busuanzi_container_page_pv">
           &nbsp热度 <span id="busuanzi_value_page_pv">
           <i class="fa fa-spinner fa-spin"></i></span>℃
        </span>
      
      
       
          <span class="post-count">
            <i class="fa fa-file-word-o"></i>&nbsp
            <span>字数统计 15k字</span>
          </span>

          <span class="post-count">
            <i class="fa fa-columns"></i>&nbsp
            <span>阅读时长 60分</span>
          </span>
      
      
    </div>

    <i class="iconfont" id="toc-eye" style="display:inline-block;color:#b36619;position:absolute;top:0;right:0;cursor:pointer;
    font-size: 24px;">&#xe61c;</i>

  </header>

  <div class="article-content">
    
      <div id="container">
        <blockquote>
<p>来自掘金小册笔记</p>
</blockquote>
<h2 id="一、Taro-的安装与使用"><a href="#一、Taro-的安装与使用" class="headerlink" title="一、Taro 的安装与使用"></a>一、Taro 的安装与使用</h2><h3 id="1-1-安装"><a href="#1-1-安装" class="headerlink" title="1.1 安装"></a>1.1 安装</h3><figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ npm install -g @tarojs/cli</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">taro -V</span><br></pre></td></tr></table></figure>
<h3 id="1-2-使用"><a href="#1-2-使用" class="headerlink" title="1.2 使用"></a>1.2 使用</h3><p>使用命令创建模板项目</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ taro init myApp</span><br></pre></td></tr></table></figure>
<p><img src="https://user-gold-cdn.xitu.io/2018/9/2/1659a045be8713ca" alt></p>
<h4 id="1-2-1-微信小程序"><a href="#1-2-1-微信小程序" class="headerlink" title="1.2.1 微信小程序"></a>1.2.1 微信小程序</h4><blockquote>
<p>选择微信小程序模式，需要自行下载并打开微信开发者工具，然后选择项目根目录进行预览</p>
</blockquote>
<p>微信小程序编译预览及打包</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># npm script</span></span><br><span class="line">$ npm run dev:weapp</span><br><span class="line">$ npm run build:weapp</span><br></pre></td></tr></table></figure>
<h4 id="1-2-2-百度小程序"><a href="#1-2-2-百度小程序" class="headerlink" title="1.2.2 百度小程序"></a>1.2.2 百度小程序</h4><blockquote>
<p>选择百度小程序模式，需要自行下载并打开<a href="https://smartprogram.baidu.com/docs/develop/devtools/show_sur/" target="_blank" rel="noopener">百度开发者工具</a>，然后在项目编译完后选择项目根目录下 dist 目录进行预览</p>
</blockquote>
<p>百度小程序编译预览及打包</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># npm script</span></span><br><span class="line">$ npm run dev:swan</span><br><span class="line">$ npm run build:swan</span><br></pre></td></tr></table></figure>
<h4 id="1-2-3-支付宝小程序"><a href="#1-2-3-支付宝小程序" class="headerlink" title="1.2.3 支付宝小程序"></a>1.2.3 支付宝小程序</h4><blockquote>
<p>选择支付宝小程序模式，需要自行下载并打开<a href="https://docs.alipay.com/mini/developer/getting-started/" target="_blank" rel="noopener">支付宝小程序开发者工具</a>，然后在项目编译完后选择项目根目录下 dist 目录进行预览</p>
</blockquote>
<p>支付宝小程序编译预览及打包：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># npm script</span></span><br><span class="line">$ npm run dev:alipay</span><br><span class="line">$ npm run build:alipay</span><br></pre></td></tr></table></figure>
<h4 id="1-2-4-H5"><a href="#1-2-4-H5" class="headerlink" title="1.2.4 H5"></a>1.2.4 H5</h4><p>H5 编译预览及打包：</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># npm script</span></span><br><span class="line">$ npm run dev:h5</span><br><span class="line"></span><br><span class="line"><span class="comment"># 仅限全局安装</span></span><br><span class="line">$ taro build --<span class="built_in">type</span> h5 --watch</span><br></pre></td></tr></table></figure>
<h4 id="1-2-5-React-Native"><a href="#1-2-5-React-Native" class="headerlink" title="1.2.5 React Native"></a>1.2.5 React Native</h4><blockquote>
<p><code>React Native</code> 端运行需执行如下命令，<code>React Native</code> 端相关的运行说明请参见 <code>React Native</code> <a href="https://nervjs.github.io/taro/docs/react-native.html" target="_blank" rel="noopener">教程</a></p>
</blockquote>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># npm script</span></span><br><span class="line">$ npm run dev:rn</span><br></pre></td></tr></table></figure>
<h3 id="1-3-更新-Taro"><a href="#1-3-更新-Taro" class="headerlink" title="1.3 更新 Taro"></a>1.3 更新 Taro</h3><blockquote>
<p><code>Taro</code> 提供了更新命令来更新 <code>CLI</code>工具自身和项目中 <code>Taro</code> 相关的依赖。</p>
</blockquote>
<p>更新 <code>taro-cli</code> 工具</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># taro</span></span><br><span class="line">$ taro update self</span><br><span class="line"><span class="comment"># npm</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>更新项目中 <code>Taro</code> 相关的依赖，这个需要在你的项目下执行</p>
</blockquote>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ taro update project</span><br></pre></td></tr></table></figure>
<h2 id="二、Taro-开发说明与注意事项"><a href="#二、Taro-开发说明与注意事项" class="headerlink" title="二、Taro 开发说明与注意事项"></a>二、Taro 开发说明与注意事项</h2><h3 id="2-1-微信小程序开发工具的配置"><a href="#2-1-微信小程序开发工具的配置" class="headerlink" title="2.1 微信小程序开发工具的配置"></a>2.1 微信小程序开发工具的配置</h3><blockquote>
<p>由于 <code>Taro</code> 编译后的代码已经经过了转义和压缩，因此还需要注意微信开发者工具的项目设置</p>
</blockquote>
<ul>
<li>设置关闭 <code>ES6</code> 转 <code>ES5</code> 功能</li>
<li>设置关闭上传代码时样式自动补全</li>
<li>设置关闭代码压缩上传</li>
</ul>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/7/1664ea0616676b32" alt></p>
<h3 id="2-2-Taro-与-React-的差异"><a href="#2-2-Taro-与-React-的差异" class="headerlink" title="2.2 Taro 与 React 的差异"></a>2.2 Taro 与 React 的差异</h3><blockquote>
<p>由于微信小程序的限制，<code>React</code> 中某些写法和特性在 <code>Taro</code> 中还未能实现，后续将会逐渐完善。 截止到本小册发布前，<code>Taro</code> 的最新版本为 <code>1.1</code>，因此以下讲解默认版本为 <code>1.1</code></p>
</blockquote>
<h4 id="2-2-1-暂不支持在-render-之外的方法定义-JSX"><a href="#2-2-1-暂不支持在-render-之外的方法定义-JSX" class="headerlink" title="2.2.1 暂不支持在 render() 之外的方法定义 JSX"></a>2.2.1 暂不支持在 render() 之外的方法定义 JSX</h4><blockquote>
<p>由于微信小程序的 <code>template</code> 不能动态传值和传入函数，<code>Taro</code> 暂时也没办法支持在类方法中定义 <code>JSX</code></p>
</blockquote>
<p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  _render() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  renderHeader(showHeader) &#123;</span><br><span class="line">    <span class="keyword">return</span> showHeader &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Header</span> /&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  renderHeader = <span class="function">(<span class="params">showHeader</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> showHeader&amp; &amp; <span class="xml"><span class="tag">&lt;<span class="name">Header</span> /&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<p>在 <code>render</code> 方法中定义</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  render () &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; showHeader, showMain &#125; = <span class="keyword">this</span>.state</span><br><span class="line">    <span class="keyword">const</span> header = showHeader &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Header</span> /&gt;</span></span></span><br><span class="line">    <span class="keyword">const</span> main = showMain &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Main</span> /&gt;</span></span></span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;View&gt;</span><br><span class="line">        &#123;header&#125;</span><br><span class="line">        &#123;main&#125;</span><br><span class="line">      &lt;<span class="regexp">/View&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;...</span></span><br></pre></td></tr></table></figure>
<h4 id="2-2-2-不能在包含-JSX-元素的-map-循环中使用-if-表达式"><a href="#2-2-2-不能在包含-JSX-元素的-map-循环中使用-if-表达式" class="headerlink" title="2.2.2 不能在包含 JSX 元素的 map 循环中使用 if 表达式"></a>2.2.2 不能在包含 JSX 元素的 map 循环中使用 if 表达式</h4><p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">numbers.map(<span class="function">(<span class="params">number</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> element = <span class="literal">null</span></span><br><span class="line">  <span class="keyword">const</span> isOdd = number % <span class="number">2</span></span><br><span class="line">  <span class="keyword">if</span> (isOdd) &#123;</span><br><span class="line">    element = <span class="xml"><span class="tag">&lt;<span class="name">Custom</span> /&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> element</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">numbers.map(<span class="function">(<span class="params">number</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> isOdd = <span class="literal">false</span></span><br><span class="line">  <span class="keyword">if</span> (number % <span class="number">2</span>) &#123;</span><br><span class="line">    isOdd = <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> isOdd &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Custom</span> /&gt;</span></span></span><br><span class="line">&#125;)...</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<p>尽量在 <code>map</code> 循环中使用条件表达式或逻辑表达式。</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">numbers.map(<span class="function">(<span class="params">number</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> isOdd = number % <span class="number">2</span></span><br><span class="line">  <span class="keyword">return</span> isOdd ? <span class="xml"><span class="tag">&lt;<span class="name">Custom</span> /&gt;</span></span> : <span class="literal">null</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">numbers.map(<span class="function">(<span class="params">number</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> isOdd = number % <span class="number">2</span></span><br><span class="line">  <span class="keyword">return</span> isOdd &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Custom</span> /&gt;</span></span></span><br><span class="line">&#125;)...</span><br></pre></td></tr></table></figure>
<h4 id="2-2-3-不能使用-Array-map-之外的方法操作-JSX-数组"><a href="#2-2-3-不能使用-Array-map-之外的方法操作-JSX-数组" class="headerlink" title="2.2.3 不能使用 Array.map 之外的方法操作 JSX 数组"></a>2.2.3 不能使用 Array.map 之外的方法操作 JSX 数组</h4><blockquote>
<p>Taro 在小程序端实际上把 JSX 转换成了字符串模板，而一个原生 <code>JSX</code> 表达式实际上是一个 <code>React/Nerv</code> 元素(react - element)的构造器，因此在原生 JSX 中你可以对任何一组 React 元素进行操作。但在 Taro 中你只能使用 map 方法，Taro 转换成小程序中 <code>wx:for</code>…</p>
</blockquote>
<p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">test.push(<span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>)</span><br><span class="line"></span><br><span class="line">numbers.forEach(<span class="function"><span class="params">numbers</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (someCase) &#123;</span><br><span class="line">    a = <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">test.shift(<span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>)</span><br><span class="line"></span><br><span class="line">components.find(<span class="function"><span class="params">component</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> component === <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">components.some(<span class="function"><span class="params">component</span> =&gt;</span> component.constructor.__proto__ === <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>.constructor)</span><br><span class="line"></span><br><span class="line">numbers.filter(<span class="built_in">Boolean</span>).map(<span class="function">(<span class="params">number</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> element = <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">&#125;)...</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<p>先处理好需要遍历的数组，然后再用处理好的数组调用 map 方法。</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">numbers.filter(isOdd).map(<span class="function">(<span class="params">number</span>) =&gt;</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index &lt; array.length; index++) &#123;</span><br><span class="line">  <span class="comment">// do you thing with array</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> element = array.map(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">&#125;)...</span><br></pre></td></tr></table></figure>
<h4 id="2-2-4-不能在-JSX-参数中使用匿名函数"><a href="#2-2-4-不能在-JSX-参数中使用匿名函数" class="headerlink" title="2.2.4 不能在 JSX 参数中使用匿名函数"></a>2.2.4 不能在 JSX 参数中使用匿名函数</h4><p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">&lt;View onClick=&#123;() =&gt; <span class="keyword">this</span>.handleClick()&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;View onClick=&#123;(e) =&gt; <span class="keyword">this</span>.handleClick(e)&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;View onClick=&#123;() =&gt; (&#123;&#125;)&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;View onClick=&#123;<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;&#125;&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;View onClick=&#123;<span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>&#123;<span class="keyword">this</span>.handleClick(e)&#125;&#125; /&gt;...</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<p>使用 <code>bind</code> 或 类参数绑定函数。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;View onClick=&#123;this.props.hanldeClick.bind(this)&#125; /&gt;</span><br></pre></td></tr></table></figure>
<h4 id="2-2-5-不能在-JSX-参数中使用对象展开符"><a href="#2-2-5-不能在-JSX-参数中使用对象展开符" class="headerlink" title="2.2.5 不能在 JSX 参数中使用对象展开符"></a>2.2.5 不能在 JSX 参数中使用对象展开符</h4><blockquote>
<p>微信小程序组件要求每一个传入组件的参数都必须预先设定好，而对象展开符则是动态传入不固定数量的参数。所以 <code>Taro</code> 没有办法支持该功能</p>
</blockquote>
<p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">&lt;View &#123;...this.props&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;View &#123;...props&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;Custom &#123;...props&#125; /&gt;</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<p>开发者自行赋值：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">render () &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; id, title &#125; = obj</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">id</span>=<span class="string">&#123;id&#125;</span> <span class="attr">title</span>=<span class="string">&#123;title&#125;</span> /&gt;</span></span></span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<h4 id="2-2-6-不允许在-JSX-参数（props）中传入-JSX-元素"><a href="#2-2-6-不允许在-JSX-参数（props）中传入-JSX-元素" class="headerlink" title="2.2.6 不允许在 JSX 参数（props）中传入 JSX 元素"></a>2.2.6 不允许在 JSX 参数（props）中传入 JSX 元素</h4><blockquote>
<p>由于微信小程序内置的组件化的系统不能通过属性（props） 传函数，而 props 传递函数可以说是 React 体系的根基之一，我们只能自己实现一套组件化系统。而自制的组件化系统不能使用内置组件化的 slot 功能。两权相害取其轻，我们暂时只能不支持该功能…</p>
</blockquote>
<p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">&lt;Custom child=&#123;&lt;View /&gt;&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;Custom child=&#123;() =&gt; <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;Custom child=&#123;<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123; &lt;View /&gt; &#125;&#125; /&gt;</span><br><span class="line"></span><br><span class="line">&lt;Custom child=&#123;ary.map(<span class="function"><span class="params">a</span> =&gt;</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>)&#125; /&gt;...</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<blockquote>
<p>通过 <code>props</code> 传值在 <code>JSX</code> 模板中预先判定显示内容，或通过 <code>props.children</code> 来嵌套子组件</p>
</blockquote>
<h4 id="2-2-7-不支持无状态组件（Stateless-Component"><a href="#2-2-7-不支持无状态组件（Stateless-Component" class="headerlink" title="2.2.7 不支持无状态组件（Stateless Component)"></a>2.2.7 不支持无状态组件（Stateless Component)</h4><blockquote>
<p>由于微信的 <code>template</code> 能力有限，不支持动态传值和函数，<code>Taro</code> 暂时只支持一个文件自定义一个组件。为了避免开发者疑惑，暂时不支持定义 <code>Stateless Component</code></p>
</blockquote>
<p>无效情况</p>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Test</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Test</span> (<span class="params">ary</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> ary.map(<span class="function"><span class="params">()</span> =&gt;</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Test = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Test = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span> /&gt;</span></span></span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p><strong>解决方案</strong></p>
<p>使用 <code>class</code> 定义组件。</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render () &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;View /&gt;</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="2-3-命名规范"><a href="#2-3-命名规范" class="headerlink" title="2.3 命名规范"></a>2.3 命名规范</h3><blockquote>
<p>Taro 函数命名使用驼峰命名法，如<code>onClick</code>，由于微信小程序的 WXML 不支持传递函数，函数名编译后会以字符串的形式绑定在 WXML 上，囿于 WXML 的限制，函数名有三项限制</p>
</blockquote>
<ul>
<li>方法名不能含有数字</li>
<li>方法名不能以下划线开头或结尾</li>
<li>方法名的长度不能大于 <code>20</code></li>
</ul>
<p>请遵守以上规则，否则编译后的代码在微信小程序中会报以下错误</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/7/1664ea05f7c23984" alt></p>
<h3 id="2-4-推荐安装-ESLint-编辑器插件"><a href="#2-4-推荐安装-ESLint-编辑器插件" class="headerlink" title="2.4 推荐安装 ESLint 编辑器插件"></a>2.4 推荐安装 ESLint 编辑器插件</h3><blockquote>
<p>Taro 有些写法跟 React 有些差异，可以通过安装 ESLint 相关的编辑器插件来获得人性化的提示。由于不同编辑器安装的插件有所不同，具体安装方法请自行搜索，这里不再赘述。 如下图，就是安装插件后获得的提示</p>
</blockquote>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/7/1664ea06098d0a4c" alt></p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/7/1664ea0608d0d42e" alt></p>
<h3 id="2-5-最佳编码方式"><a href="#2-5-最佳编码方式" class="headerlink" title="2.5 最佳编码方式"></a>2.5 最佳编码方式</h3><p><strong>组件传递函数属性名以 on 开头</strong></p>
<blockquote>
<p>在 <code>Taro</code> 中，父组件要往子组件传递函数，属性名必须以<code>on</code> 开头</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 调用 Custom 组件，传入 handleEvent 函数，属性名为 `onTrigger`</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Parent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  handleEvent () &#123;</span><br><span class="line"></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  render () &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;Custom onTrigger=&#123;<span class="keyword">this</span>.handleEvent&#125;&gt;&lt;<span class="regexp">/Custom&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;...</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>这是因为，微信小程序端组件化是不能直接传递函数类型给子组件的，在 Taro 中是借助组件的事件机制来实现这一特性，而小程序中传入事件的时候属性名写法为 <code>bindmyevent</code> 或者 <code>bind:myevent</code></p>
</blockquote>
<figure class="highlight jsx"><table><tr><td class="code"><pre><span class="line">&lt;!-- 当自定义组件触发“myevent”事件时，调用“onMyEvent”方法 --&gt;</span><br><span class="line">&lt;component-tag-name bindmyevent=<span class="string">"onMyEvent"</span> /&gt;</span><br><span class="line">&lt;!-- 或者可以写成 --&gt;</span><br><span class="line">&lt;component-tag-name bind:myevent=<span class="string">"onMyEvent"</span> /&gt;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>所以 <code>Taro</code>中约定组件传递函数属性名以 <code>on</code> 开头，同时这也和内置组件的事件绑定写法保持一致了…</p>
</blockquote>
<p><strong>小程序端不要在组件中打印传入的函数</strong></p>
<blockquote>
<p>前面已经提到小程序端的组件传入函数的原理，所以在小程序端不要在组件中打印传入的函数，因为拿不到结果，但是 <code>this.props.onXxx &amp;&amp; this.props.onXxx()</code> 这种判断函数是否传入来进行调用的写法是完全支持的…</p>
</blockquote>
<p><strong>小程序端不要将在模板中用到的数据设置为 undefined</strong></p>
<ul>
<li>由于小程序不支持将 <code>data</code> 中任何一项的 <code>value</code> 设为 <code>undefined</code> ，在 <code>setState</code> 的时候也请避免这么用。你可以使用 <code>null</code> 来替代。</li>
<li>小程序端不要在组件中打印 <code>this.props.children</code><br>在微信小程序端是通过<code>&lt;slot /&gt;</code> 来实现往自定义组件中传入元素的，而 <code>Taro</code> 利用 <code>this.props.children</code> 在编译时实现了这一功能， <code>this.props.children</code> 会直接被编译成 <code>&lt;slot /&gt;</code> 标签，所以它在小程序端属于语法糖的存在，请不要在组件中打印它…</li>
</ul>
<p><strong>组件 state 与 props 里字段重名的问题</strong></p>
<blockquote>
<p>不要在 <code>state</code>与 <code>props</code> 上用同名的字段，因为这些被字段在微信小程序中都会挂在 <code>data</code> 上</p>
</blockquote>
<p><strong>小程序中页面生命周期 componentWillMount 不一致问题</strong></p>
<blockquote>
<p>由于微信小程序里页面在 <code>onLoad</code> 时才能拿到页面的路由参数，而页面 <code>onLoad</code> 前组件都已经 <code>attached</code> 了。因此页面的 <code>componentWillMount</code> 可能会与预期不太一致。例如：</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误写法</span></span><br><span class="line">render () &#123;</span><br><span class="line">  <span class="comment">// 在 willMount 之前无法拿到路由参数</span></span><br><span class="line">  <span class="keyword">const</span> abc = <span class="keyword">this</span>.$router.params.abc</span><br><span class="line">  <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">Custom</span> <span class="attr">adc</span>=<span class="string">&#123;abc&#125;</span> /&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确写法</span></span><br><span class="line">componentWillMount () &#123;</span><br><span class="line">  <span class="keyword">const</span> abc = <span class="keyword">this</span>.$router.params.abc</span><br><span class="line">  <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">    abc</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line">render () &#123;</span><br><span class="line">  <span class="comment">// 增加一个兼容判断</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>.state.abc &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Custom</span> <span class="attr">adc</span>=<span class="string">&#123;abc&#125;</span> /&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对于不需要等到页面 <code>willMount</code> 之后取路由参数的页面则没有任何影响…</p>
<p><strong>JS 编码必须用单引号</strong></p>
<blockquote>
<p>在 <code>Taro</code> 中，<code>JS</code> 代码里必须书写单引号，特别是 <code>JSX</code> 中，如果出现双引号，可能会导致编译错误</p>
</blockquote>
<p><strong>process.env 的使用</strong></p>
<blockquote>
<p>不要以解构的方式来获取通过 <code>env</code>配置的 <code>process.env</code> 环境变量，请直接以完整书写的方式 <code>process.env.NODE_ENV</code>来进行使用</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 错误写法，不支持</span></span><br><span class="line"><span class="keyword">const</span> &#123; NODE_ENV = <span class="string">'development'</span> &#125; = process.env</span><br><span class="line"><span class="keyword">if</span> (NODE_ENV === <span class="string">'development'</span>) &#123;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确写法</span></span><br><span class="line"><span class="keyword">if</span> (process.env.NODE_ENV === <span class="string">'development'</span>) &#123;</span><br><span class="line"></span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p><strong>预加载</strong></p>
<blockquote>
<p>在微信小程序中，从调用 <code>Taro.navigateTo</code>、<code>Taro.redirectTo</code> 或 <code>Taro.switchTab</code> 后，到页面触发<code>componentWillMount</code> 会有一定延时。因此一些网络请求可以提前到发起跳转前一刻去请求</p>
</blockquote>
<p>Taro 提供了 <code>componentWillPreload</code> 钩子，它接收页面跳转的参数作为参数。可以把需要预加载的内容通过 <code>return</code> 返回，然后在页面触发 <code>componentWillMount</code> 后即可通过 <code>this.$preloadData</code> 获取到预加载的内容。…</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  componentWillMount () &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'isFetching: '</span>, <span class="keyword">this</span>.isFetching)</span><br><span class="line">    <span class="keyword">this</span>.$preloadData</span><br><span class="line">      .then(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'res: '</span>, res)</span><br><span class="line">        <span class="keyword">this</span>.isFetching = <span class="literal">false</span></span><br><span class="line">      &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  componentWillPreload (params) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.fetchData(params.url)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  fetchData () &#123;</span><br><span class="line">    <span class="keyword">this</span>.isFetching = <span class="literal">true</span></span><br><span class="line">    ...</span><br><span class="line">  &#125;</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<h2 id="三、Taro-设计思想及架构"><a href="#三、Taro-设计思想及架构" class="headerlink" title="三、Taro 设计思想及架构"></a>三、Taro 设计思想及架构</h2><blockquote>
<p>在 Taro 中采用的是编译原理的思想，所谓编译原理，就是一个对输入的源代码进行语法分析，语法树构建，随后对语法树进行转换操作再解析生成目标代码的过程。</p>
</blockquote>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/1665182480dfc020" alt></p>
<h3 id="3-1-抹平多端差异"><a href="#3-1-抹平多端差异" class="headerlink" title="3.1 抹平多端差异"></a>3.1 抹平多端差异</h3><blockquote>
<p>基于编译原理，我们已经可以将 Taro 源码编译成不同端上可以运行的代码了，但是这对于实现多端开发还是远远不够。因为不同的平台都有自己的特性，每一个平台都不尽相同，这些差异主要体现在不同的组件标准与不同的 API 标准以及不同的运行机制上</p>
</blockquote>
<p>以小程序和 Web 端为例</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/1665182486f397d9" alt></p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/1665182487386fef" alt></p>
<ul>
<li>可以看出小程序和 Web 端上组件标准与 API 标准有很大差异，这些差异仅仅通过代码编译手段是无法抹平的，例如你不能直接在编译时将小程序的 <code>&lt;view /&gt;</code> 直接编译成 <code>&lt;div /&gt;</code>，因为他们虽然看上去有些类似，但是他们的组件属性有很大不同的，仅仅依靠代码编译，无法做到一致，同理，众多 <code>API</code> 也面临一样的情况。针对这样的情况，<code>Taro</code> 采用了定制一套运行时标准来抹平不同平台之间的差异。</li>
<li>这一套标准主要以三个部分组成，包括标准运行时框架、标准基础组件库、标准端能力 API，其中运行时框架和 API 对应 <code>@taro/taro</code>，组件库对应 <code>@tarojs/components</code>，通过在不同端实现这些标准，从而达到去差异化的目的…</li>
</ul>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/16651824884a5682" alt></p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/16651824b8ac59a4" alt></p>
<h2 id="四、CLI-原理及不同端的运行机制"><a href="#四、CLI-原理及不同端的运行机制" class="headerlink" title="四、CLI 原理及不同端的运行机制"></a>四、CLI 原理及不同端的运行机制</h2><h3 id="4-1-taro-cli-包"><a href="#4-1-taro-cli-包" class="headerlink" title="4.1 taro-cli 包"></a>4.1 taro-cli 包</h3><h4 id="4-1-1-Taro-命令"><a href="#4-1-1-Taro-命令" class="headerlink" title="4.1.1 Taro 命令"></a>4.1.1 Taro 命令</h4><blockquote>
<p><code>taro-cli</code> 包位于 <code>Taro</code> 工程的 <code>Packages</code> 目录下，通过 <code>npm install -g @tarojs/cli</code> 全局安装后，将会生成一个 <code>Taro</code> 命令。主要负责项目初始化、编译、构建等。直接在命令行输入 <code>Taro</code> ，会看到如下提示…</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">➜ taro</span><br><span class="line">👽 Taro v0<span class="number">.0</span><span class="number">.63</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  Usage: taro &lt;command&gt; [options]</span><br><span class="line"></span><br><span class="line">  Options:</span><br><span class="line"></span><br><span class="line">    -V, --version       output the version number</span><br><span class="line">    -h, --help          output usage information</span><br><span class="line"></span><br><span class="line">  Commands:</span><br><span class="line"></span><br><span class="line">    init [projectName]  Init a project <span class="keyword">with</span> <span class="keyword">default</span> templete</span><br><span class="line">    build               Build a project <span class="keyword">with</span> options</span><br><span class="line">    update              Update packages <span class="keyword">of</span> taro</span><br><span class="line">    help [cmd]          display help <span class="keyword">for</span> [cmd]...</span><br></pre></td></tr></table></figure>
<p>里面包含了 Taro 所有命令用法及作用。</p>
<h4 id="4-1-2-包管理与发布"><a href="#4-1-2-包管理与发布" class="headerlink" title="4.1.2 包管理与发布"></a>4.1.2 包管理与发布</h4><ul>
<li>首先，我们需要了解 <code>taro-cli</code> 包与 <code>Taro</code> 工程的关系。</li>
<li>将 <code>Taro</code> 工程 <code>Clone</code> 之后，可以看到工程的目录结构如下，整体结构还是比较清晰的：</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── CHANGELOG.md</span><br><span class="line">├── LICENSE</span><br><span class="line">├── README.md</span><br><span class="line">├── build</span><br><span class="line">├── docs</span><br><span class="line">├── lerna-debug.log</span><br><span class="line">├── lerna.json        // Lerna 配置文件</span><br><span class="line">├── package.json</span><br><span class="line">├── packages</span><br><span class="line">│   ├── eslint-config-taro</span><br><span class="line">│   ├── eslint-plugin-taro</span><br><span class="line">│   ├── postcss-plugin-constparse</span><br><span class="line">│   ├── postcss-pxtransform</span><br><span class="line">│   ├── taro</span><br><span class="line">│   ├── taro-async-await</span><br><span class="line">│   ├── taro-cli</span><br><span class="line">│   ├── taro-components</span><br><span class="line">│   ├── taro-components-rn</span><br><span class="line">│   ├── taro-h5</span><br><span class="line">│   ├── taro-plugin-babel</span><br><span class="line">│   ├── taro-plugin-csso</span><br><span class="line">│   ├── taro-plugin-sass</span><br><span class="line">│   ├── taro-plugin-uglifyjs</span><br><span class="line">│   ├── taro-redux</span><br><span class="line">│   ├── taro-redux-h5</span><br><span class="line">│   ├── taro-rn</span><br><span class="line">│   ├── taro-rn-runner</span><br><span class="line">│   ├── taro-router</span><br><span class="line">│   ├── taro-transformer-wx</span><br><span class="line">│   ├── taro-weapp</span><br><span class="line">│   └── taro-webpack-runner</span><br><span class="line">└── yarn.lock...</span><br></pre></td></tr></table></figure>
<blockquote>
<p><code>Taro</code> 项目主要是由一系列 <code>NPM</code> 包组成，位于工程的 <code>Packages</code> 目录下。它的包管理方式和 <code>Babel</code> 项目一样，将整个项目作为一个 <code>monorepo</code> 来进行管理，并且同样使用了包管理工具 <code>Lerna</code></p>
</blockquote>
<p><code>Packages</code> 目录下十几个包中，最常用的项目初始化与构建的命令行工具 <code>Taro CLI</code> 就是其中一个。在 <code>Taro</code> 工程根目录运行 <code>lerna publish</code> 命令之后，<code>lerna.json</code> 里面配置好的所有的包会被发布到 <code>NPM</code> 上</p>
<h4 id="4-1-3-taro-cli-包的目录结构如下"><a href="#4-1-3-taro-cli-包的目录结构如下" class="headerlink" title="4.1.3 taro-cli 包的目录结构如下"></a>4.1.3 taro-cli 包的目录结构如下</h4><figure class="highlight js"><table><tr><td class="code"><pre><span class="line">./</span><br><span class="line">├── bin        <span class="comment">// 命令行</span></span><br><span class="line">│   ├── taro              <span class="comment">// taro 命令</span></span><br><span class="line">│   ├── taro-build        <span class="comment">// taro build 命令</span></span><br><span class="line">│   ├── taro-update       <span class="comment">// taro update 命令</span></span><br><span class="line">│   └── taro-init         <span class="comment">// taro init 命令</span></span><br><span class="line">├── package.json</span><br><span class="line">├── node_modules</span><br><span class="line">├── src</span><br><span class="line">│   ├── build.js        <span class="comment">// taro build 命令调用，根据 type 类型调用不同的脚本</span></span><br><span class="line">│   ├── config</span><br><span class="line">│   │   ├── babel.js        <span class="comment">// Babel 配置</span></span><br><span class="line">│   │   ├── babylon.js      <span class="comment">// JavaScript 解析器 babylon 配置</span></span><br><span class="line">│   │   ├── browser_list.js <span class="comment">// autoprefixer browsers 配置</span></span><br><span class="line">│   │   ├── index.js        <span class="comment">// 目录名及入口文件名相关配置</span></span><br><span class="line">│   │   └── uglify.js</span><br><span class="line">│   ├── creator.js</span><br><span class="line">│   ├── h5.js       <span class="comment">// 构建h5 平台代码</span></span><br><span class="line">│   ├── project.js  <span class="comment">// taro init 命令调用，初始化项目</span></span><br><span class="line">│   ├── rn.js       <span class="comment">// 构建React Native 平台代码</span></span><br><span class="line">│   ├── util        <span class="comment">// 一系列工具函数</span></span><br><span class="line">│   │   ├── index.js</span><br><span class="line">│   │   ├── npm.js</span><br><span class="line">│   │   └── resolve_npm_files.js</span><br><span class="line">│   └── weapp.js        <span class="comment">// 构建小程序代码转换</span></span><br><span class="line">├── templates           <span class="comment">// 脚手架模版</span></span><br><span class="line">│   └── <span class="keyword">default</span></span><br><span class="line">│       ├── appjs</span><br><span class="line">│       ├── config</span><br><span class="line">│       │   ├── dev</span><br><span class="line">│       │   ├── index</span><br><span class="line">│       │   └── prod</span><br><span class="line">│       ├── editorconfig</span><br><span class="line">│       ├── eslintrc</span><br><span class="line">│       ├── gitignor...</span><br></pre></td></tr></table></figure>
<blockquote>
<p>通过上面的目录树可以发现，<code>taro-cli</code> 工程的文件并不算多，主要目录有：<code>/bin</code>、<code>/src</code>、<code>/template</code></p>
</blockquote>
<h3 id="4-2-用到的核心库"><a href="#4-2-用到的核心库" class="headerlink" title="4.2 用到的核心库"></a>4.2 用到的核心库</h3><ul>
<li><a href="https://github.com/tj/commander.js/" target="_blank" rel="noopener">tj/commander.js</a> Node.js - 命令行接口全面的解决方案</li>
<li><a href="https://github.com/jprichardson/node-fs-extra" target="_blank" rel="noopener">jprichardson/node-fs-extra</a> - 在 Node.js 的 fs 基础上增加了一些新的方法，更好用，还可以拷贝模板。</li>
<li><a href="https://github.com/chalk/chalk" target="_blank" rel="noopener">chalk/chalk</a> - 可以用于控制终端输出字符串的样式</li>
<li><a href="https://github.com/SBoudrias/Inquirer.js/" target="_blank" rel="noopener">SBoudrias/Inquirer.js - Node.js</a> 命令行交互工具，通用的命令行用户界面集合，可以和用户进行交互</li>
<li><a href="https://github.com/sindresorhus/ora" target="_blank" rel="noopener">sindresorhus/ora</a> - 实现加载中的状态是一个 Loading 加前面转起来的小圈圈，成功了是一个 Success 加前面一个小钩钩</li>
<li><a href="https://github.com/sboudrias/mem-fs-editor" target="_blank" rel="noopener">SBoudrias/mem-fs-editor</a> - 提供一系列 API，方便操作模板文件</li>
<li><a href="https://github.com/shelljs/shelljs" target="_blank" rel="noopener">shelljs/shelljs </a> - ShellJS 是 Node.js 扩展，用于实现 Unix shell 命令执行。</li>
</ul>
<h3 id="4-3-Taro-Init"><a href="#4-3-Taro-Init" class="headerlink" title="4.3 Taro Init"></a>4.3 Taro Init</h3><p><img src="https://user-gold-cdn.xitu.io/2018/10/8/16651547b6ddebe1" alt></p>
<blockquote>
<p>当我们全局安装 <code>taro-cli</code> 包之后，我们的命令行里就有了 Taro 命令</p>
</blockquote>
<ul>
<li>那么 <code>Taro</code> 命令是怎样添加进去的呢？其原因在于 <code>package.json</code> 里面的 <code>bin</code>字段：</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="string">"bin"</span>: &#123;</span><br><span class="line">    <span class="string">"taro"</span>: <span class="string">"bin/taro"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面代码指定，Taro 命令对应的可执行文件为 <code>bin/taro</code> 。NPM 会寻找这个文件，在 <code>[prefix]/bin</code> 目录下建立符号链接。在上面的例子中，Taro 会建立符号链接 <code>[prefix]/bin/taro</code>。由于 <code>[prefix]/bin</code>目录会在运行时加入系统的 PATH 变量，因此在运行 NPM 时，就可以不带路径，直接通过命令来调用这些脚本。</p>
<ul>
<li>关于<code>prefix</code>，可以通过<code>npm config get prefix</code>获取。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ npm config get prefix</span><br><span class="line">/usr/local</span><br></pre></td></tr></table></figure>
<p>通过下列命令可以更加清晰的看到它们之间的符号链接…</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ ls -al `which taro`</span><br><span class="line">lrwxr-xr-x  1 chengshuai  admin  40  6 15 10:51 /usr/local/bin/taro -&gt; ../lib/node_modules/@tarojs/cli/bin/taro...</span><br></pre></td></tr></table></figure>
<h4 id="4-3-1-命令关联与参数解析"><a href="#4-3-1-命令关联与参数解析" class="headerlink" title="4.3.1 命令关联与参数解析"></a>4.3.1 命令关联与参数解析</h4><blockquote>
<p>这里就不得不提到一个有用的包：<code>tj/commander.js</code> ，<code>Node.js</code> 命令行接口全面的解决方案，灵感来自于 Ruby’s commander。可以自动的解析命令和参数，合并多选项，处理短参等等，功能强大，上手简单</p>
</blockquote>
<p>更主要的，<code>commander</code> 支持 <code>Git</code> 风格的子命令处理，可以根据子命令自动引导到以特定格式命名的命令执行文件，文件名的格式是 <code>[command]-[subcommand]</code>，例如</p>
<ul>
<li><code>taro init</code> =&gt; <code>taro-init</code></li>
<li><code>taro build</code> =&gt; <code>taro-build</code></li>
<li><code>/bin/taro</code> 文件内容不多，核心代码也就那几行 <code>.command()</code> 命令：</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="meta">#! /usr/bin/env node</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> program = <span class="built_in">require</span>(<span class="string">'commander'</span>)</span><br><span class="line"><span class="keyword">const</span> &#123;getPkgVersion&#125; = <span class="built_in">require</span>(<span class="string">'../src/util'</span>)</span><br><span class="line"></span><br><span class="line">program</span><br><span class="line">  .version(getPkgVersion())</span><br><span class="line">  .usage(<span class="string">'&lt;command&gt; [options]'</span>)</span><br><span class="line">  .command(<span class="string">'init [projectName]'</span>, <span class="string">'Init a project with default templete'</span>)</span><br><span class="line">  .command(<span class="string">'build'</span>, <span class="string">'Build a project with options'</span>)</span><br><span class="line">  .command(<span class="string">'update'</span>, <span class="string">'Update packages of taro'</span>)</span><br><span class="line">  .parse(process.argv)...</span><br></pre></td></tr></table></figure>
<blockquote>
<p>通过上面代码可以发现，<code>init</code>，<code>build</code>    ，<code>update</code>等命令都是通过<code>.command(name, description)</code>方法定义的，然后通过 <code>.parse(arg)</code> 方法解析参数</p>
</blockquote>
<h4 id="4-3-2-参数解析及与用户交互"><a href="#4-3-2-参数解析及与用户交互" class="headerlink" title="4.3.2 参数解析及与用户交互"></a>4.3.2 参数解析及与用户交互</h4><ul>
<li><code>commander</code> 包可以自动解析命令和参数，在配置好命令之后，还能够自动生成 <code>help</code>（帮助）命令和<code>version</code>（版本查看） 命令。并且通过<code>program.args</code>便可以获取命令行的参数，然后再根据参数来调用不同的脚本。</li>
<li>但当我们运行 <code>taro init</code> 命令后，如下所示的命令行交互又是怎么实现的呢？…</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">$ taro init taroDemo</span><br><span class="line">Taro 即将创建一个新项目!</span><br><span class="line">Need help? Go and open issue: https://github.com/NervJS/taro/issues/new</span><br><span class="line"></span><br><span class="line">Taro v0.0.50</span><br><span class="line"></span><br><span class="line">? 请输入项目介绍！</span><br><span class="line">? 请选择模板 默认模板...</span><br></pre></td></tr></table></figure>
<p>这里使用的是 <code>SBoudrias/Inquirer.js</code> 来处理命令行交互。</p>
<p>用法其实很简单</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> inquirer = <span class="built_in">require</span>(<span class="string">'inquirer'</span>)  <span class="comment">// npm i inquirer -D</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">typeof</span> conf.description !== <span class="string">'string'</span>) &#123;</span><br><span class="line">      prompts.push(&#123;</span><br><span class="line">        type: <span class="string">'input'</span>,</span><br><span class="line">        name: <span class="string">'description'</span>,</span><br><span class="line">        message: <span class="string">'请输入项目介绍！'</span></span><br><span class="line">      &#125;)</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<ul>
<li><code>prompt()</code>接受一个问题对象的数据，在用户与终端交互过程中，将用户的输入存放在一个答案对象中，然后返回一个<code>Promise</code>，通过<code>then()</code>获取到这个答案对象。<br>借此，新项目的名称、版本号、描述等信息可以直接通过终端交互插入到项目模板中，完善交互流程。</li>
<li>当然，交互的问题不仅限于此，可以根据自己项目的情况，添加更多的交互问题。<code>inquirer.js</code>强大的地方在于，支持很多种交互类型，除了简单的input，还有<code>confirm</code>、<code>list</code>、<code>password</code>、<code>checkbox</code>等，具体可以参见项目的工程 README。<br>此外，你在执行异步操作的过程中，还可以使用 <code>sindresorhus/ora</code> 来添加一下 <code>Loading</code> 效果。使用 <code>chalk/chalk</code> 给终端的输出添加各种样式…</li>
</ul>
<h4 id="4-3-3-模版文件操作"><a href="#4-3-3-模版文件操作" class="headerlink" title="4.3.3 模版文件操作"></a>4.3.3 模版文件操作</h4><p><strong>最后就是模版文件操作了，主要分为两大块：</strong></p>
<ul>
<li>将输入的内容插入到模板中</li>
<li>根据命令创建对应目录结构，copy 文件</li>
<li>更新已存在文件内容</li>
</ul>
<blockquote>
<p>这些操作基本都是在 <code>/template/index.js</code> 文件里。<br>这里还用到了 <code>shelljs/shelljs</code> 执行 <code>shell</code> 脚本，如初始化 <code>Git： git init</code>，项目初始化之后安装依赖 <code>npm install</code>等</p>
</blockquote>
<p><strong>拷贝模板文件</strong></p>
<blockquote>
<p>拷贝模版文件主要是使用 <code>jprichardson/node-fs-extra</code>的 <code>copyTpl()</code>方法，此方法使用 <code>ejs</code> 模板语法，可以将输入的内容插入到模版的对应位置：</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">this</span>.fs.copyTpl(</span><br><span class="line">  project,</span><br><span class="line">  path.join(projectPath, <span class="string">'project.config.json'</span>),</span><br><span class="line">  &#123;description, projectName&#125;</span><br><span class="line">);...</span><br></pre></td></tr></table></figure>
<h3 id="4-4-Taro-Build"><a href="#4-4-Taro-Build" class="headerlink" title="4.4 Taro Build"></a>4.4 Taro Build</h3><ul>
<li><code>taro build</code> 命令是整个 <code>Taro</code> 项目的灵魂和核心，主要负责多端代码编译（H5，小程序，<code>React Native</code>等）。</li>
<li><code>Taro</code> 命令的关联，参数解析等和 <code>taro init</code> 其实是一模一样的，那么最关键的代码转换部分是怎样实现的呢？…</li>
</ul>
<h4 id="4-4-1-编译工作流与抽象语法树（AST）"><a href="#4-4-1-编译工作流与抽象语法树（AST）" class="headerlink" title="4.4.1 编译工作流与抽象语法树（AST）"></a>4.4.1 编译工作流与抽象语法树（AST）</h4><blockquote>
<p>Taro 的核心部分就是将代码编译成其他端（H5、小程序、React Native 等）代码。一般来说，将一种结构化语言的代码编译成另一种类似的结构化语言的代码包括以下几个步骤</p>
</blockquote>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515483b7fa7c0" alt></p>
<p>首先是 <code>Parse</code>，将代码解析（<code>Parse</code>）成抽象语法树（Abstract Syntex Tree），然后对 <code>AST</code>进行遍历（<code>traverse</code>）和替换(<code>replace</code>)（这对于前端来说其实并不陌生，可以类比 <code>DOM</code> 树的操作），最后是生成（<code>generate</code>），根据新的 <code>AST</code> 生成编译后的代码…</p>
<h4 id="4-4-2-Babel-模块"><a href="#4-4-2-Babel-模块" class="headerlink" title="4.4.2 Babel 模块"></a>4.4.2 Babel 模块</h4><p><code>Babel</code> 是一个通用的多功能的 JavaScript编译器，更确切地说是源码到源码的编译器，通常也叫做转换编译器（transpiler）。 意思是说你为 Babel 提供一些 JavaScript 代码，Babel 更改这些代码，然后返回给你新生成的代码…</p>
<h4 id="4-4-3-解析页面-Config-配置"><a href="#4-4-3-解析页面-Config-配置" class="headerlink" title="4.4.3 解析页面 Config 配置"></a>4.4.3 解析页面 Config 配置</h4><blockquote>
<p>在业务代码编译成小程序的代码过程中，有一步是将页面入口 JS 的 Config 属性解析出来，并写入 <code>*.json</code> 文件，供小程序使用。那么这一步是怎么实现的呢？这里将这部分功能的关键代码抽取出来：</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. babel-traverse方法， 遍历和更新节点</span></span><br><span class="line">traverse(ast, &#123;  </span><br><span class="line">  ClassProperty(astPath) &#123; <span class="comment">// 遍历类的属性声明</span></span><br><span class="line">    <span class="keyword">const</span> node = astPath.node</span><br><span class="line">    <span class="keyword">if</span> (node.key.name === <span class="string">'config'</span>) &#123; <span class="comment">// 类的属性名为 config</span></span><br><span class="line">      configObj = traverseObjectNode(node)</span><br><span class="line">      astPath.remove() <span class="comment">// 将该方法移除掉</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 遍历，解析为 JSON 对象</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">traverseObjectNode</span>(<span class="params">node, obj</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="string">'ClassProperty'</span> || node.type === <span class="string">'ObjectProperty'</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> properties = node.value.properties</span><br><span class="line">      obj = &#123;&#125;</span><br><span class="line">      properties.forEach(<span class="function">(<span class="params">p, index</span>) =&gt;</span> &#123;</span><br><span class="line">        obj[p.key.name] = traverseObjectNode(p.value)</span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="keyword">return</span> obj</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="string">'ObjectExpression'</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> properties = node.properties</span><br><span class="line">    obj = &#123;&#125;</span><br><span class="line">    properties.forEach(<span class="function">(<span class="params">p, index</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// const t = require('babel-types')  AST 节点的 Lodash 式工具库</span></span><br><span class="line">      <span class="keyword">const</span> key = t.isIdentifier(p.key) ? p.key.name : p.key.value</span><br><span class="line">      obj[key] = traverseObjectNode(p.value)</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> obj</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="string">'ArrayExpression'</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> node.elements.map(<span class="function"><span class="params">item</span> =&gt;</span> traverseObjectNode(item))</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure>
<h2 id="五、Taro-组件库及-API-的设计与适配"><a href="#五、Taro-组件库及-API-的设计与适配" class="headerlink" title="五、Taro 组件库及 API 的设计与适配"></a>五、Taro 组件库及 API 的设计与适配</h2><h3 id="5-1-多端差异"><a href="#5-1-多端差异" class="headerlink" title="5.1 多端差异"></a>5.1 多端差异</h3><h4 id="5-1-1-组件差异"><a href="#5-1-1-组件差异" class="headerlink" title="5.1.1 组件差异"></a>5.1.1 组件差异</h4><p>小程序、H5 以及快应用都可以划分为 XML 类，React Native 归为 JSX 类，两种语言风牛马不相及，给适配设置了非常大的障碍。XML 类有个明显的特点是关注点分离（Separation of Concerns），即语义层（XML）、视觉层（CSS）、交互层（JavaScript）三者分离的松耦合形式，JSX 类则要把三者混为一体，用脚本来包揽三者的工作…</p>
<p><strong>不同端的组件的差异还体现在定制程度上</strong></p>
<ul>
<li>H5 标签（组件）提供最基础的功能——布局、表单、媒体、图形等等；</li>
<li>小程序组件相对 H5 有了一定程度的定制，我们可以把小程序组件看作一套类似于 H5 的 UI 组件库；</li>
<li>React Native 端组件也同样如此，而且基本是专“组”专用的，比如要触发点击事件就得用 Touchable 或者 Text 组件，要渲染文本就得用 Text 组件（虽然小程序也提供了 Text 组件，但它的文本仍然可以直接放到 view 之类的组件里）…</li>
</ul>
<h4 id="5-1-2-API-差异"><a href="#5-1-2-API-差异" class="headerlink" title="5.1.2 API 差异"></a>5.1.2 API 差异</h4><p><strong>各端 API 的差异具有定制化、接口不一、能力限制的特点</strong></p>
<ul>
<li>定制化：各端所提供的 API 都是经过量身打造的，比如小程序的开放接口类 API，完全是针对小程序所处的微信环境打造的，其提供的功能以及外在表现都已由框架提供实现，用户上手可用，毋须关心内部实现。</li>
<li>接口不一：相同的功能，在不同端下的调用方式以及调用参数等也不一样，比如 <code>socket</code>，小程序中用 <code>wx.connectSocket</code> 来连接，<code>H5</code> 则用 <code>new WebSocket()</code> 来连接，这样的例子我们可以找到很多个。</li>
<li>能力限制：各端之间的差异可以进行定制适配，然而并不是所有的 <code>API</code>（此处特指小程序 <code>API</code>，因为多端适配是向小程序看齐的）在各个端都能通过定制适配来实现，因为不同端所能提供的端能力“大异小同”，这是在适配过程中不可抗拒、不可抹平的差异…</li>
</ul>
<h3 id="5-2-多端适配"><a href="#5-2-多端适配" class="headerlink" title="5.2 多端适配"></a>5.2 多端适配</h3><h4 id="5-2-1-样式处理"><a href="#5-2-1-样式处理" class="headerlink" title="5.2.1 样式处理"></a>5.2.1 样式处理</h4><p>H5 端使用官方提供的 WEUI 进行适配，React Native 端则在组件内添加样式，并通过脚本来控制一些状态类的样式，框架核心在编译的时候把源代码的 class 所指向的样式通过 css-to-react-native 进行转译，所得 StyleSheet 样式传入组件的 style 参数，组件内部会对样式进行二次处理，得到最终的样式…</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/1665155932b630fe" alt></p>
<p><strong>为什么需要对样式进行二次处理？</strong></p>
<blockquote>
<p>部分组件是直接把传入 <code>style</code> 的样式赋给最外层的 <code>React Native</code> 原生组件，但部分经过层层封装的组件则不然，我们要把容器样式、内部样式和文本样式离析。为了方便解释，我们把这类组件简化为以下的形式：</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;View style=&#123;wrapperStyle&#125;&gt;</span><br><span class="line">  &lt;View style=&#123;containerStyle&#125;&gt;</span><br><span class="line">    &lt;Text style=&#123;textStyle&#125;&gt;Hello World&lt;/Text&gt;</span><br><span class="line">  &lt;/View&gt;</span><br><span class="line">&lt;/View&gt;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>假设组件有样式 <code>margin-top</code>、<code>background-color</code> 和 <code>font-size</code>，转译传入组件后，就要把分别把它们传到<code>wrapperStyle</code>、<code>containerStyle</code> 和 <code>textStyle</code>，可参考 <code>ScrollView</code> 的 <code>style</code> 和 <code>contentContainerStyle</code>…</p>
</blockquote>
<h4 id="5-2-2-组件封装"><a href="#5-2-2-组件封装" class="headerlink" title="5.2.2 组件封装"></a>5.2.2 组件封装</h4><blockquote>
<p>组件的封装则是一个“仿制”的过程，利用端提供的原材料，加工成通用的组件，暴露相对统一的调用方式。我们用 <code>&lt;Button /&gt;</code> 这个组件来举例，在小程序端它也许是长这样子的</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;button size=&quot;mini&quot; plain=&#123;&#123;plain&#125;&#125; loading=&#123;&#123;loading&#125;&#125; hover-class=&quot;you-hover-me&quot;&gt;&lt;/button&gt;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>如果要实现 <code>H5</code> 端这么一个按钮，大概会像下面这样，在组件内部把小程序的按钮特性实现一遍，然后暴露跟小程序一致的调用方式，就完成了 <code>H5</code> 端一个组件的设计</p>
</blockquote>
<figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span></span></span><br><span class="line"><span class="tag">  &#123;<span class="attr">...omit</span>(<span class="attr">this.props</span>, ['<span class="attr">hoverClass</span>', '<span class="attr">onTouchStart</span>', '<span class="attr">onTouchEnd</span>'])&#125;</span></span><br><span class="line"><span class="tag">  <span class="attr">className</span>=<span class="string">&#123;cls&#125;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">style</span>=<span class="string">&#123;style&#125;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">onClick</span>=<span class="string">&#123;onClick&#125;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">disabled</span>=<span class="string">&#123;disabled&#125;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">onTouchStart</span>=<span class="string">&#123;_onTouchStart&#125;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">onTouchEnd</span>=<span class="string">&#123;_onTouchEnd&#125;</span></span></span><br><span class="line"><span class="tag">&gt;</span></span><br><span class="line">  &#123;loading &amp;&amp; <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">'weui-loading'</span> /&gt;</span>&#125;</span><br><span class="line">  &#123;children&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">button</span>&gt;</span>...</span><br></pre></td></tr></table></figure>
<ul>
<li>其他端的组件适配相对 H5 端来说会更曲折复杂一些，因为 H5 跟小程序的语言较为相似，而其他端需要整合特定端的各种组件，以及利用端组件的特性来实现，比如在 React Native 中实现这个按钮，则需要用到 <code>&lt;Touchable* /&gt;</code>、<code>&lt;View /&gt;</code>、<code>&lt;Text /&gt;</code>，要实现动画则需要用上 <code>&lt;Animated.View /&gt;</code>，还有就是相对于 H5 和小程序比较容易实现的 touch 事件，在 React Native 中则需要用上 PanResponder 来进行“仿真”，总之就是，因“端”制宜，一切为了最后只需一行代码通行多端！</li>
<li>除了属性支持外，事件回调的参数也需要进行统一，为此，需要在内部进行处理，比如 Input 的 <code>onInput</code> 事件，需要给它造一个类似小程序相同事件的回调参数，比如 <code>{ target: { value: text }</code>, <code>detail: { value: text }</code> }，这样，开发者们就可以像下面这样处理回调事件，无需关心中间发生了什么…</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">onInputHandler</span> (<span class="params">&#123; target, detail &#125;</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(target.value, detail.value)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="六、JSX-转换微信小程序模板的实现"><a href="#六、JSX-转换微信小程序模板的实现" class="headerlink" title="六、JSX 转换微信小程序模板的实现"></a>六、JSX 转换微信小程序模板的实现</h2><h3 id="6-1-代码的本质"><a href="#6-1-代码的本质" class="headerlink" title="6.1 代码的本质"></a>6.1 代码的本质</h3><blockquote>
<p>不管是任意语言的代码，其实它们都有两个共同点</p>
</blockquote>
<ul>
<li>它们都是由字符串构成的文本</li>
<li>它们都要遵循自己的语言规范</li>
</ul>
<p>第一点很好理解，既然代码是字符串构成的，我们要修改/编译代码的最简单的方法就是使用字符串的各种正则表达式。例如我们要将 <code>JSON</code> 中一个键名 <code>foo</code> 改为 <code>bar</code>，只要写一个简单的正则表达式就能做到：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">jsonStr.replace(/(?&lt;=&quot;)foo(?=&quot;\s*:)/i, &apos;bar&apos;)...</span><br></pre></td></tr></table></figure>
<blockquote>
<p>编译就是把一段字符串改成另外一段字符串</p>
</blockquote>
<h3 id="6-2-Babel"><a href="#6-2-Babel" class="headerlink" title="6.2 Babel"></a>6.2 Babel</h3><blockquote>
<p><code>JavaScript</code> 社区其实有非常多 <code>parser</code> 实现，比如 <code>Acorn</code>、<code>Esprima</code>、<code>Recast</code>、<code>Traceur</code>、<code>Cherow</code> 等等。但我们还是选择使用 <code>Babel</code>，主要有以下几个原因</p>
</blockquote>
<ul>
<li><code>Babel</code> 可以解析还没有进入 ECMAScript 规范的语法。例如装饰器这样的提案，虽然现在没有进入标准但是已经广泛使用有一段时间了；</li>
<li><code>Babel</code> 提供插件机制解析 <code>TypeScript</code>、<code>Flow</code>、<code>JSX</code>这样的 <code>JavaScript</code> 超集，不必单独处理这些语言；</li>
<li><code>Babel</code> 拥有庞大的生态，有非常多的文档和样例代码可供参考；<br>除去 <code>parser</code> 本身，<code>Babel</code> 还提供各种方便的工具库可以优化、生成、调试代码…</li>
</ul>
<p><strong>Babylon（ @babel/parser）</strong></p>
<blockquote>
<p><code>Babylon</code> 就是 <code>Babel</code> 的 <code>parser</code>。它可以把一段符合规范的 JavaScript 代码输出成一个符合 Esprima 规范的 <code>AST</code>。 大部分 <code>parser</code> 生成的 <code>AST</code> 数据结构都遵循 Esprima 规范，包括 ESLint 的 <code>parser</code> ESTree。这就意味着我们熟悉了 Esprima 规范的 <code>AST</code> 数据结构还能去写 ESLint 插件。</p>
</blockquote>
<p>我们可以尝试解析 <code>n * n</code> 这句简单的表达式：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> babylon <span class="keyword">from</span> <span class="string">"babylon"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> code = <span class="string">`n * n`</span>;</span><br><span class="line"></span><br><span class="line">babylon.parse(code);...</span><br></pre></td></tr></table></figure>
<p>最终 <code>Babylon</code> 会解析成这样的数据结构：</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/1665157669296bc1" alt></p>
<blockquote>
<p>你也可以使用 <a href="https://astexplorer.net/" target="_blank" rel="noopener">ASTExploroer</a> 快速地查看代码的 <code>AST</code></p>
</blockquote>
<p><strong>Babel-traverse (@babel/traverse)</strong></p>
<blockquote>
<p><code>babel-traverse</code> 可以遍历由 Babylon 生成的抽象语法树，并把抽象语法树的各个节点从拓扑数据结构转化成一颗路径（Path）树，Path 表示两个节点之间连接的响应式（Reactive）对象，它拥有添加、删除、替换节点等方法。当你调用这些修改树的方法之后，路径信息也会被更新。除此之外，Path 还提供了一些操作作用域（Scope） 和标识符绑定（Identifier Binding） 的方法可以去做处理一些更精细复杂的需求。可以说 <code>babel-traverse</code> 是使用 Babel 作为编译器最核心的模块…</p>
</blockquote>
<p>让我们尝试一下把一段代码中的 <code>n * n</code> 变为 <code>x * x</code></p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> babylon <span class="keyword">from</span> <span class="string">"@babel/parser"</span>;</span><br><span class="line"><span class="keyword">import</span> traverse <span class="keyword">from</span> <span class="string">"babel-traverse"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> code = <span class="string">`function square(n) &#123;</span></span><br><span class="line"><span class="string">  return n * n;</span></span><br><span class="line"><span class="string">&#125;`</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ast = babylon.parse(code);</span><br><span class="line"></span><br><span class="line">traverse(ast, &#123;</span><br><span class="line">  enter(path) &#123;</span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">      path.node.type === <span class="string">"Identifier"</span> &amp;&amp;</span><br><span class="line">      path.node.name === <span class="string">"n"</span></span><br><span class="line">    ) &#123;</span><br><span class="line">      path.node.name = <span class="string">"x"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);...</span><br></pre></td></tr></table></figure>
<p><strong>Babel-types（@babel/types）</strong></p>
<blockquote>
<p><code>babel-types</code> 是一个用于 <code>AST</code> 节点的 <code>Lodash</code> 式工具库，它包含了构造、验证以及变换 <code>AST</code>节点的方法。 该工具库包含考虑周到的工具方法，对编写处理 <code>AST</code> 逻辑非常有用。例如我们之前在 <code>babel-traverse</code>中改变标识符 n 的代码可以简写为：</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">import traverse from &quot;babel-traverse&quot;;</span><br><span class="line">import * as t from &quot;babel-types&quot;;</span><br><span class="line"></span><br><span class="line">traverse(ast, &#123;</span><br><span class="line">  enter(path) &#123;</span><br><span class="line">    if (t.isIdentifier(path.node, &#123; name: &quot;n&quot; &#125;)) &#123;</span><br><span class="line">      path.node.name = &quot;x&quot;;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>可以发现使用 <code>babel-types</code>能提高我们转换代码的可读性，在配合 TypeScript 这样的静态类型语言后，<code>babel-types</code> 的方法还能提供类型校验的功能，能有效地提高我们转换代码的健壮性和可靠性…</p>
</blockquote>
<h3 id="6-3-实践例子"><a href="#6-3-实践例子" class="headerlink" title="6.3 实践例子"></a>6.3 实践例子</h3><p>以一个简单 <code>Page</code> 页面为例：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> Taro, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'@tarojs/taro'</span></span><br><span class="line"><span class="keyword">import</span> &#123; View, Text &#125; <span class="keyword">from</span> <span class="string">'@tarojs/components'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Home</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  config = &#123;</span><br><span class="line">    navigationBarTitleText: <span class="string">'首页'</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  state = &#123;</span><br><span class="line">    numbers: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  handleClick = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.props.onTest()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  render () &#123;</span><br><span class="line">    <span class="keyword">const</span> oddNumbers = <span class="keyword">this</span>.state.numbers.filter(<span class="function"><span class="params">number</span> =&gt;</span> number &amp; <span class="number">2</span>)</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;ScrollView className=<span class="string">'home'</span> scrollTop=&#123;<span class="literal">false</span>&#125;&gt;</span><br><span class="line">        奇数：</span><br><span class="line">        &#123;</span><br><span class="line">          oddNumbers.map(<span class="function"><span class="params">number</span> =&gt;</span> <span class="xml"><span class="tag">&lt;<span class="name">Text</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span>)</span><br><span class="line">        &#125;</span><br><span class="line">        偶数：</span><br><span class="line">        &#123;</span><br><span class="line">          numbers.map(<span class="function"><span class="params">number</span> =&gt;</span> number % <span class="number">2</span> === <span class="number">0</span> &amp;&amp; <span class="xml"><span class="tag">&lt;<span class="name">Text</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span>)</span><br><span class="line">        &#125;</span><br><span class="line">      &lt;<span class="regexp">/ScrollView&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;...</span></span><br></pre></td></tr></table></figure>
<h4 id="6-3-1-设计思路"><a href="#6-3-1-设计思路" class="headerlink" title="6.3.1 设计思路"></a>6.3.1 设计思路</h4><ul>
<li>Taro 的结构主要分两个方面：运行时和编译时。运行时负责把编译后到代码运行在本不能运行的对应环境中，你可以把 Taro 运行时理解为前端开发当中 <code>polyfill</code>。举例来说，小程序新建一个页面是使用 <code>Page</code> 方法传入一个字面量对象，并不支持使用类。如果全部依赖编译时的话，那么我们要做到事情大概就是把类转化成对象，把 <code>state</code> 变为 <code>data</code>，把生命周期例如 componentDidMount 转化成 <code>onReady</code>，把事件由可能的类函数（<code>Class method</code>）和类属性函数(<code>Class property function</code>) 转化成字面量对象方法（Object <code>property function</code>）等等。</li>
<li>但这显然会让我们的编译时工作变得非常繁重，在一个类异常复杂时出错的概率也会变高。但我们有更好的办法：实现一个 <code>createPage</code> 方法，接受一个类作为参数，返回一个小程序 <code>Page</code> 方法所需要的字面量对象。这样不仅简化了编译时的工作，我们还可以在 <code>createPage</code> 对编译时产出的类做各种操作和优化。通过运行时把工作分离了之后，再编译时我们只需要在文件底部加上一行代码 <code>Page(createPage(componentName))</code> 即可…</li>
</ul>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/1665157cb5a81196" alt></p>
<ul>
<li>回到一开始那段代码，我们定义了一个类属性 <code>config</code>，<code>config</code> 是一个对象表达式（Object Expression），这个对象表达式只接受键值为标识符（Identifier）或字符串，而键名只能是基本类型。这样简单的情况我们只需要把这个对象表达式转换为 <code>JSON</code> 即可。另外一个类属性 <code>state</code> 在 <code>Page</code> 当中有点像是小程序的 <code>data</code>，但它在多数情况不是完整的 <code>data</code>。这里我们不用做过多的操作，<code>babel</code>的插件 <code>transform-class-proerties</code> 会把它编译到类的构造器中。函数 <code>handleClick</code> 我们交给运行时处理，有兴趣的同学可以跳到 Taro 运行时原理查看具体技术细节。</li>
<li>再来看我们的 <code>render()</code>函数，它的第一行代码通过 <code>filter</code> 把数字数组的所有偶数项都过滤掉，真正用来循环的是 <code>oddNumbers</code>，而 <code>oddNumbers</code> 并没有在 <code>this.state</code> 中，所以我们必须手动把它加入到 <code>this.state</code>。和 <code>React 一样，Taro 每次更新都会调用 render 函数，但和 React 不同的是，React 的 render</code> 是一个创建虚拟 DOM 的方法，而 Taro 的 render 会被重命名为 <code>_createData</code>，它是一个创建数据的方法：在 <code>JSX</code> 使用过的数据都在这里被创建最后放到小程序 <code>Page</code> 或 <code>Component</code> 工厂方法中的 <code>data</code>。最终我们的 <code>render</code> 方法会被编译为…</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">_createData() &#123;</span><br><span class="line">  <span class="keyword">this</span>.__state = <span class="built_in">arguments</span>[<span class="number">0</span>] || <span class="keyword">this</span>.state || &#123;&#125;;</span><br><span class="line">  <span class="keyword">this</span>.__props = <span class="built_in">arguments</span>[<span class="number">1</span>] || <span class="keyword">this</span>.props || &#123;&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> oddNumbers = <span class="keyword">this</span>.__state.numbers.filter(<span class="function"><span class="params">number</span> =&gt;</span> number &amp; <span class="number">2</span>);</span><br><span class="line">  <span class="built_in">Object</span>.assign(<span class="keyword">this</span>.__state, &#123;</span><br><span class="line">    oddNumbers: oddNumbers</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">this</span>.__state;</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<h4 id="6-3-2-WXML-和-JSX"><a href="#6-3-2-WXML-和-JSX" class="headerlink" title="6.3.2 WXML 和 JSX"></a>6.3.2 WXML 和 JSX</h4><p>在 Taro 里 <code>render</code> 的所有 <code>JSX</code>元素都会在 JavaScript 文件中被移除，它们最终将会编译成小程序的 <code>WXML</code>。每个 <code>WXML</code> 元素和 <code>HTML</code> 元素一样，我们可以把它定义为三种类型：<code>Element</code>、<code>Text</code>、<code>Comment</code>。其中 <code>Text</code> 只有一个属性: 内容（<code>content</code>），它对应的 <code>AST</code> 类型是 <code>JSXText</code>，我们只需要将前文源码中对应字符串的奇数和偶数转换成 Text 即可。而对于 <code>Comment</code>而言我们可以将它们全部清除，不参与 WXML 的编译。Element 类型有它的名字（<code>tagName</code>）、<code>children</code>、属性（<code>attributes</code>），其中 <code>children</code> 可能是任意 <code>WXML</code> 类型，属性是一个对象，键值和键名都是字符串。我们将把重点放在如何转换成为 <code>WXML</code> 的 <code>Element</code>类型。</p>
<p>首先我们可以先看 <code>&lt;View className=&#39;home&#39;&gt;</code>，它在 <code>AST</code>中是一个 <code>JSXElement，它的结构和我们定义</code>Element <code>类型差不多。我们先将 JSXElement 的</code>ScrollView 从驼峰式的 JSX 命名转化为短横线（kebab case）风格，className 和 <code>scrollTop</code>的值分别代表了 <code>JSXAttribute</code> 值的两种类型：<code>StringLiteral</code> 和 <code>JSXExpressionContainer</code>，<code>className</code> 是简单的 <code>StringLiteral</code> 处理起来很方便，<code>scrollTop</code> 处理起来稍微麻烦点，我们需要用两个花括号<code>{}</code> 把内容包起来…</p>
<p>接下来我们再思考一下每一个 JSXElement 出现的位置，你可以发现其实它的父元素只有几种可能性：return、循环、条件（逻辑）表达式。而在上一篇文章中我们提到，babel-traverse 遍历的 AST 类型是响应式的——也就是说只要我们按照 JSXElement 父元素类型的顺序穷举处理这几种可能性，把各种可能性大结果应用到 JSX 元素之后删除掉原来的表达式，最后就可以把一个复杂的 JSX 表达式转换为一个简单的 WXML 数据结构。…</p>
<p>我们先看第一个循环：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">oddNumbers.map(number =&gt; &lt;Text onClick=&#123;this.handleClick&#125;&gt;&#123;number&#125;&lt;/Text&gt;)</span><br></pre></td></tr></table></figure>
<p>Text 的父元素是一个 map 函数（CallExpression），我们可以把函数的 callee: oddNumbers 作为 wx:for 的值，并把它放到 state 中，匿名函数的第一个参数是 wx:for-item的值，函数的第二个参数应该是 wx:for-index 的值，但代码中没有传所以我们可以不管它。然后我们把这两个 wx: 开头的参数作为 attribute 传入 Text 元素就完成了循环的处理。而对于 onClick 而言，在 Taro 中 on 开头的元素参数都是事件，所以我们只要把 this. 去掉即可。Text 元素的 children 是一个 JSXExpressionContainer，我们按照之前的处理方式处理即可。最后这行我们生成出来的数据结构应该是这样…</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  type: <span class="string">'element'</span>,</span><br><span class="line">  tagName: <span class="string">'text'</span>,</span><br><span class="line">  attributes: [</span><br><span class="line">    &#123; <span class="attr">bindtap</span>: <span class="string">'handleClick'</span> &#125;,</span><br><span class="line">    &#123; <span class="string">'wx:for'</span>: <span class="string">'&#123;&#123;oddNumbers&#125;&#125;'</span> &#125;,</span><br><span class="line">    &#123; <span class="string">'wx:for-item'</span>: <span class="string">'number'</span> &#125;</span><br><span class="line">  ],</span><br><span class="line">  children: [</span><br><span class="line">    &#123; <span class="attr">type</span>: <span class="string">'text'</span>, <span class="attr">content</span>: <span class="string">'&#123;&#123;number&#125;&#125;'</span> &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p>有了这个数据结构生成一段 WXML 就非常简单了</p>
<p>再来看第二个循环表达式：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">numbers.map(number =&gt; number % 2 === 0 &amp;&amp; &lt;Text onClick=&#123;this.handleClick&#125;&gt;&#123;number&#125;&lt;/Text&gt;)...</span><br></pre></td></tr></table></figure>
<p>它比第一个循环表达式多了一个逻辑表达式（Logical Operators），我们知道 expr1 &amp;&amp; expr2 意味着如果 expr1 能转换成 true 则返回 expr2，也就是说我们只要把 number % 2 === 0 作为值生成一个键名 wx:if 的 JSXAttribute 即可。但由于 wx:if 和 wx:for 同时作用于一个元素可能会出现问题，所以我们应该生成一个 block 元素，把 wx:if 挂载到 block 元素，原元素则全部作为 children 传入 block 元素中。这时 babel-traverse 会检测到新的元素 block，它的父元素是一个 map 循环函数，因此我们可以按照第一个循环表达式的处理方法来处理这个表达式。</p>
<p>这里我们可以思考一下 <code>this.props.text || this.props.children</code> 的解决方案。当用户在 JSX 中使用 || 作为逻辑表达式时很可能是 this.props.text 和 this.props.children 都有可能作为结果返回。这里 Taro 将它编译成了 <code>this.props.text ? this.props.text: this.props.children</code>，按照条件表达式（三元表达式）的逻辑，也就是说会生成两个 block，一个 <code>wx:if</code> 和一个 <code>wx:else</code>：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&lt;block wx:<span class="keyword">if</span>=<span class="string">"&#123;&#123;text&#125;&#125;"</span>&gt;&#123;&#123;text&#125;&#125;&lt;<span class="regexp">/block&gt;</span></span><br><span class="line"><span class="regexp">&lt;block wx:else&gt;</span></span><br><span class="line"><span class="regexp">    &lt;slot&gt;&lt;/</span>slot&gt;</span><br><span class="line">&lt;<span class="regexp">/block&gt;</span></span><br></pre></td></tr></table></figure>
<h2 id="七、小程序运行时"><a href="#七、小程序运行时" class="headerlink" title="七、小程序运行时"></a>七、小程序运行时</h2><p>为了使 <code>Taro</code> 组件转换成小程序组件并运行在小程序环境下， <code>Taro</code> 主要做了两个方面的工作：编译以及运行时适配。编译过程会做很多工作，例如：将 JSX 转换成小程序 <code>.wxml</code> 模板，生成小程序的配置文件、页面及组件的代码等等。编译生成好的代码仍然不能直接运行在小程序环境里，那运行时又是如何与之协同工作的呢？…</p>
<h3 id="7-1-注册程序、页面以及自定义组件"><a href="#7-1-注册程序、页面以及自定义组件" class="headerlink" title="7.1 注册程序、页面以及自定义组件"></a>7.1 注册程序、页面以及自定义组件</h3><p>在小程序中会区分程序、页面以及组件，通过调用对应的函数，并传入包含生命周期回调、事件处理函数等配置内容的 object 参数来进行注册：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">Component(&#123;</span><br><span class="line">  data: &#123;&#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    handleClick () &#123;&#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>而在 <code>Taro</code>里，它们都是一个组件类：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CustomComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  state = &#123; &#125;</span><br><span class="line">  handleClick () &#123; &#125;</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<ul>
<li>那么 <code>Taro</code> 的组件类是如何转换成小程序的程序、页面或组件的呢？</li>
<li>例如，有一个组件：<code>customComponent</code>，编译过程会在组件底部添加一行这样的代码（此处代码作示例用，与实际项目生成的代码不尽相同）：</li>
</ul>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">Component(createComponent(customComponent))</span><br></pre></td></tr></table></figure>
<ul>
<li><code>createComponent</code> 方法是整个运行时的入口，在运行的时候，会根据传入的组件类，返回一个组件的配置对象</li>
</ul>
<blockquote>
<p>在小程序里，程序的功能及配置与页面和组件差异较大，因此运行时提供了两个方法 <code>createApp</code> 和 <code>createComponent</code> 来分别创建程序和组件（页面）。<code>createApp</code> 的实现非常简单</p>
</blockquote>
<p><strong>createComponent 方法主要做了这样几件事情</strong>：</p>
<ul>
<li>将组件的<code>state</code>转换成小程序组件配置对象的 <code>data</code></li>
<li>将组件的生命周期对应到小程序组件的生命周期</li>
<li>将组件的事件处理函数对应到小程序的事件处理函数</li>
</ul>
<h3 id="7-2-组件-state-转换"><a href="#7-2-组件-state-转换" class="headerlink" title="7.2 组件 state 转换"></a>7.2 组件 state 转换</h3><p>其实在 Taro（React） 组件里，除了组件的 <code>state</code>，<code>JSX</code> 里还可以访问<code>props</code> 、<code>render</code> 函数里定义的值、以及任何作用域上的成员。而在小程序中，与模板绑定的数据均来自对应页面（或组件）的 <code>data</code>。因此 <code>JSX</code> 模板里访问到的数据都会对应到小程序组件的 <code>data</code> 上。接下来我们通过列表渲染的例子来说明<code>state</code>和 <code>data</code>是如何对应的…</p>
<p><strong>在 JSX 里访问 state</strong></p>
<blockquote>
<p>在小程序的组件上使用 <code>wx:for</code> 绑定一个数组，就可以实现循环渲染。例如，在 Taro 里你可能会这么写：</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123; </span><br><span class="line">  state = &#123;</span><br><span class="line">    list: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">  &#125;</span><br><span class="line">  render () &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;View&gt;</span><br><span class="line">        &#123;<span class="keyword">this</span>.state.list.map(<span class="function"><span class="params">item</span> =&gt;</span> <span class="xml"><span class="tag">&lt;<span class="name">View</span>&gt;</span>&#123;item&#125;<span class="tag">&lt;/<span class="name">View</span>&gt;</span></span>)&#125;</span><br><span class="line">      &lt;<span class="regexp">/View&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br></pre></td></tr></table></figure>
<p>编译后的小程序组件模板：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;view&gt;</span><br><span class="line">  &lt;view wx:for=&quot;&#123;&#123;list&#125;&#125;&quot; wx:for-item=&quot;item&quot;&gt;&#123;&#123;item&#125;&#125;&lt;/view&gt; </span><br><span class="line">&lt;/view&gt;</span><br></pre></td></tr></table></figure>
<p>其中 <code>state.list</code> 只需直接对应到小程序（页面）组件的 <code>data.list</code> 上即可…</p>
<p><strong>在 render 里生成了新的变量</strong></p>
<p>然而事情通常没有那么简单，在 Taro 里也可以这么用</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    list = [1, 2, 3]</span><br><span class="line">  &#125;</span><br><span class="line">  render () &#123;</span><br><span class="line">    return (</span><br><span class="line">      &lt;View&gt;</span><br><span class="line">        &#123;this.state.list.map(item =&gt; ++item).map(item =&gt; &lt;View&gt;&#123;item&#125;&lt;/View&gt;)&#125;</span><br><span class="line">      &lt;/View&gt;</span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>编译后的小程序组件模板是这样的：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;view&gt;</span><br><span class="line">  &lt;view wx:for=&quot;&#123;&#123;$anonymousCallee__1&#125;&#125;&quot; wx:for-item=&quot;item&quot;&gt;&#123;&#123;item&#125;&#125;&lt;/view&gt; </span><br><span class="line">&lt;/view&gt;...</span><br></pre></td></tr></table></figure>
<blockquote>
<p>在编译时会给 Taro 组件创建一个 <code>_createData</code>的方法，里面会生成 <code>$anonymousCallee__1</code> 这个变量， $<code>anonymousCallee__1</code> 是由编译器生成的，对 <code>this.state.list</code> 进行相关操作后的变量。 <code>$anonymousCallee__1</code> 最终会被放到组件的 data 中给模板调用：</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> $anonymousCallee__1 = <span class="keyword">this</span>.state.list.map(<span class="function"><span class="keyword">function</span> (<span class="params">item</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> ++item;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<blockquote>
<p><code>render</code> 里 <code>return</code>之前的所有定义变量或者对 <code>props</code>、<code>state</code> 计算产生新变量的操作，都会被编译到 <code>_createData</code> 方法里执行，这一点在前面 JSX 编译成小程序模板的相关文章中已经提到。每当 Taro 调用 <code>this.setState</code> API 来更新数据时，都会调用生成的 <code>_createData</code>来获取最新数据…</p>
</blockquote>
<h3 id="7-3-将组件的生命周期对应到小程序组件的生命周期"><a href="#7-3-将组件的生命周期对应到小程序组件的生命周期" class="headerlink" title="7.3 将组件的生命周期对应到小程序组件的生命周期"></a>7.3 将组件的生命周期对应到小程序组件的生命周期</h3><blockquote>
<p>初始化过程里的生命周期对应很简单，在小程序的生命周期回调函数里调用 Taro 组件里对应的生命周期函数即可，例如：小程序组件 <code>ready</code> 的回调函数里会调用 Taro 组件的 <code>componentDidMount</code> 方法。它们的执行过程和对应关系如下图…</p>
</blockquote>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515c132121443" alt></p>
<blockquote>
<p>小程序页面的<code>componentWillMount</code> 有一点特殊，会有两种初始化方式。由于小程序的页面需要等到 <code>onLoad</code> 之后才可以获取到页面的路由参数，因此如果是启动页面，会等到 <code>onLoad</code> 时才会触发。而对于小程序内部通过 <code>navigateTo</code>等 API 跳转的页面，Taro 做了一个兼容，调用 <code>navigateTo</code> 时将页面参数存储在一个全局对象中，在页面 <code>attached</code> 的时候从全局对象里取到，这样就不用等到页面 <code>onLoad</code> 即可获取到路由参数，触发 <code>componentWillMount</code>生命周期…</p>
</blockquote>
<p><strong>状态更新</strong></p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515c132336584" alt></p>
<ul>
<li>Taro 组件的 <code>setState</code> 行为最终会对应到小程序的<code>setData</code>。Taro 引入了如 <code>nextTick</code> ，编译时识别模板中用到的数据，在 setData 前进行数据差异比较等方式来提高 <code>setState</code>的性能。</li>
<li>如上图，组件调用 <code>setState</code> 方法之后，并不会立刻执行组件更新逻辑，而是会将最新的 <code>state</code> 暂存入一个数组中，等 <code>nextTick</code> 回调时才会计算最新的 <code>state</code> 进行组件更新。这样即使连续多次的调用<code>setState</code> 并不会触发多次的视图更新。在小程序中 <code>nextTick</code> 是这么实现的…</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> nextTick = <span class="function">(<span class="params">fn, ...args</span>) =&gt;</span> &#123;</span><br><span class="line">  fn = <span class="keyword">typeof</span> fn === <span class="string">'function'</span> ? fn.bind(<span class="literal">null</span>, ...args) : fn</span><br><span class="line">  <span class="keyword">const</span> timerFunc = wx.nextTick ? wx.nextTick : setTimeout</span><br><span class="line">  timerFunc(fn)</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<blockquote>
<p>除了计算出最新的组件 <code>state</code> ，在组件状态更新过程里还会调用前面提到过的 <code>_createData</code> 方法，得到最终小程序组件的 <code>data</code>，并调用小程序的 <code>setData</code> 方法来进行组件的更新</p>
</blockquote>
<h3 id="7-4-事件处理函数对应"><a href="#7-4-事件处理函数对应" class="headerlink" title="7.4 事件处理函数对应"></a>7.4 事件处理函数对应</h3><p>在小程序的组件里，事件响应函数需要配置在 methods 字段里。而在 JSX 里，事件是这样绑定的：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;View onClick=&#123;this.handleClick&#125;&gt;&lt;/View&gt;</span><br></pre></td></tr></table></figure>
<p>编译的过程会将 JSX 转换成小程序模板：</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;view bindclick=&quot;handleClick&quot;&gt;&lt;/view&gt;...</span><br></pre></td></tr></table></figure>
<p>在<code>createComponent</code> 方法里，会将事件响应函数 <code>handleClick</code> 添加到 <code>methods</code> 字段中，并且在响应函数里调用真正的 <code>this.handleClick</code>方法。</p>
<p>在编译过程中，会提取模板中绑定过的方法，并存到组件的 <code>$events</code> 字段里，这样在运行时就可以只将用到的事件响应函数配置到小程序组件的 <code>methods</code> 字段中。</p>
<p>在运行时通过 <code>processEvent</code> 这个方法来处理事件的对应，省略掉处理过程，就是这样的…</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">processEvent</span> (<span class="params">eventHandlerName, obj</span>) </span>&#123;</span><br><span class="line">  obj[eventHandlerName] = <span class="function"><span class="keyword">function</span> (<span class="params">event</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">	scope[eventHandlerName].apply(callScope, realArgs)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>这个方法的核心作用就是解析出事件响应函数执行时真正的作用域 <code>callScope</code> 以及传入的参数。在 <code>JSX</code>里，我们可以像下面这样通过 <code>bind</code>传入参数：</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&lt;View onClick=&#123;this.handleClick.bind(this, arga, argb)&#125;&gt;&lt;/View&gt;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>小程序不支持通过 <code>bind</code> 的方式传入参数，但是小程序可以用 <code>data</code> 开头的方式，将数据传递到 <code>event.currentTarget.dataset</code> 中。编译过程会将 <code>bind</code> 方式传递的参数对应到<code>dataset</code> 中，<code>processEvent</code> 函数会从 <code>dataset</code> 里取到传入的参数传给真正的事件响应函数。</p>
</blockquote>
<p>至此，经过编译之后的 Taro 组件终于可以运行在小程序环境里了…</p>
<h3 id="7-5-对-API-进行-Promise-化的处理"><a href="#7-5-对-API-进行-Promise-化的处理" class="headerlink" title="7.5 对 API 进行 Promise 化的处理"></a>7.5 对 API 进行 Promise 化的处理</h3><blockquote>
<p>Taro 对小程序的所有 API 进行了一个分类整理，将其中的异步 API 做了一层 <code>Promise</code>化的封装。例如，<code>wx.getStorage</code>经过下面的处理对应到<code>Taro.getStorage</code>(此处代码作示例用，与实际源代码不尽相同)</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">Taro[<span class="string">'getStorage'</span>] = <span class="function"><span class="params">options</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> obj = <span class="built_in">Object</span>.assign(&#123;&#125;, options)</span><br><span class="line">  <span class="keyword">const</span> p = <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">	[<span class="string">'fail'</span>, <span class="string">'success'</span>, <span class="string">'complete'</span>].forEach(<span class="function">(<span class="params">k</span>) =&gt;</span> &#123;</span><br><span class="line">	  obj[k] = <span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">	    options[k] &amp;&amp; options[k](res)</span><br><span class="line">	    <span class="keyword">if</span> (k === <span class="string">'success'</span>) &#123;</span><br><span class="line">		  resolve(res)</span><br><span class="line">	    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (k === <span class="string">'fail'</span>) &#123;</span><br><span class="line">		  reject(res)</span><br><span class="line">	    &#125;</span><br><span class="line">	  &#125;</span><br><span class="line">	&#125;)</span><br><span class="line">	wx[<span class="string">'getStorage'</span>](obj)</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="keyword">return</span> p</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p>就可以这么调用了：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 小程序的调用方式</span></span><br><span class="line">Taro.getStorage(&#123;</span><br><span class="line">  key: <span class="string">'test'</span>,</span><br><span class="line">  success() &#123;</span><br><span class="line">	</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 在 Taro 里也可以这样调用</span></span><br><span class="line">Taro.getStorage(&#123;</span><br><span class="line">  key: <span class="string">'test'</span></span><br><span class="line">&#125;).then(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// success</span></span><br><span class="line">&#125;)...</span><br></pre></td></tr></table></figure>
<h2 id="八、H5-运行时"><a href="#八、H5-运行时" class="headerlink" title="八、H5 运行时"></a>八、H5 运行时</h2><h3 id="8-1-H5-运行时解析"><a href="#8-1-H5-运行时解析" class="headerlink" title="8.1 H5 运行时解析"></a>8.1 H5 运行时解析</h3><blockquote>
<p>首先，我们选用<code>Nerv</code>作为 <code>Web</code> 端的运行时框架。你可能会有问题：同样是类<code>React</code>框架，为何我们不直接用<code>React</code>，而是用<code>Nerv</code>呢？</p>
</blockquote>
<p>为了更快更稳。开发过程中前端框架本身有可能会出现问题。如果是第三方框架，很有可能无法得到及时的修复，导致整个项目的进度受影响。Nerv就不一样。作为团队自研的产品，出现任何问题我们都可以在团队内部快速得到解决。与此同时，Nerv也具有与React相同的 API，同样使用 Virtual DOM 技术进行优化，正常使用与React并没有区别，完全可以满足我们的需要。</p>
<p>使用Taro之后，我们书写的是类似于下图的代码…</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12e8fe10" alt></p>
<blockquote>
<p>我们注意到，就算是转换过的代码，也依然存在着<code>view</code>、<code>button</code>等在 <code>Web</code> 开发中并不存在的组件。如何在 <code>Web</code> 端正常使用这些组件？这是我们碰到的第一个问题</p>
</blockquote>
<h4 id="8-1-1-组件实现"><a href="#8-1-1-组件实现" class="headerlink" title="8.1.1 组件实现"></a>8.1.1 组件实现</h4><p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12d7da84" alt></p>
<p>作为开发者，你第一反应或许会尝试在编译阶段下功夫，尝试直接使用效果类似的 Web 组件替代：用<code>div</code>替代<code>view</code>，用<code>img</code>替代<code>image</code>，以此类推。</p>
<p>费劲心机搞定标签转换之后，上面这个差异似乎是解决了。但很快你就会碰到一些更加棘手的问题：<code>hover-start-time</code>、<code>hover-stay-time</code>等等这些常规 Web 开发中并不存在的属性要如何处理？</p>
<p>回顾一下：在前面讲到多端转换的时候，我们说到了<code>babel</code>。在Taro中，我们使用<code>babylon</code>生成 <code>AST</code>，<code>babel-traverse</code>去修改和移动 <code>AST</code> 中的节点。但babel所做的工作远远不止这些。</p>
<p>我们不妨去<code>babel</code>的 <code>playground</code> 看一看代码在转译前后的对比：在使用了<code>@babel/preset-env</code>的<code>BUILT-INS</code>之后，简单的一句源码<code>new Map()</code>，在<code>babel</code>编译后却变成了好几行代码…</p>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12e9969d" alt></p>
<p>注意看这几个文件：<code>core-js/modules/web.dom.iterable</code>，<code>core-js/modules/es6.array.iterator</code>，<code>core-js/modules/es6.map</code>。我们可以在<code>core-js</code>的 <code>Git</code> 仓库找到他们的真身。很明显，这几个模块就是对应的 es 特性运行时的实现。</p>
<p>从某种角度上讲，我们要做的事情和babel非常像。babel把基于新版 ECMAScript 规范的代码转换为基于旧 <code>ECMAScript</code> 规范的代码，而Taro希望把基于React语法的代码转换为小程序的语法。我们从babel受到了启发：既然<code>babel</code>可以通过运行时框架来实现新特性，那我们也同样可以通过运行时代码，实现上面这些 Web 开发中不存在的功能。</p>
<p>举个例子。对于<code>view</code>组件，首先它是个普通的类 <code>React</code> 组件，它把它的子组件如实展示出来…</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> Nerv, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'nervjs'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">View</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      &lt;div&gt;&#123;<span class="keyword">this</span>.props.children&#125;&lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">    );</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;...</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>接下来，我们需要对<code>hover-start-time</code>做处理。与Taro其他地方的命名规范一致，我们这个<code>View</code>组件接受的属性名将会是驼峰命名法：<code>hoverStartTime</code>。<code>hoverStartTime</code>参数决定我们将在<code>View</code>组件触发<code>touch</code>事件多久后改变组件的样式…</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 示例代码</span></span><br><span class="line">render() &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123;</span><br><span class="line">    hoverStartTime = <span class="number">50</span>,</span><br><span class="line">    onTouchStart</span><br><span class="line">  &#125; = <span class="keyword">this</span>.props;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> _onTouchStart = <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">    setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// @TODO 触发touch样式改变</span></span><br><span class="line">    &#125;, hoverStartTime);</span><br><span class="line">    onTouchStart &amp;&amp; onTouchStart(e);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    &lt;div onTouchStart=&#123;_onTouchStart&#125;&gt;</span><br><span class="line">      &#123;<span class="keyword">this</span>.props.children&#125;</span><br><span class="line">    &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">  );</span></span><br><span class="line"><span class="regexp">&#125;...</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>再稍加修饰，我们就能得到一个功能完整的<code>Web</code>版 <code>View</code>组件</p>
</blockquote>
<p><code>view</code>可以说是小程序最简单的组件之一了。<code>text</code>的实现甚至比上面的代码还要简单得多。但这并不说明组件的实现之路上就没有障碍。复杂如<code>swiper</code>，<code>scroll-view</code>，<code>tabbar</code>，我们需要花费大量的精力分析小程序原生组件的 <code>API</code>，交互行为，极端值处理，接受的属性等等，再通过 Web 技术实现。…</p>
<h3 id="8-2-API-适配"><a href="#8-2-API-适配" class="headerlink" title="8.2 API 适配"></a>8.2 API 适配</h3><blockquote>
<p>除了组件，小程序下有一些 API 也是 Web 开发中所不具备的。比如小程序框架内置的<code>wx.request/wx.getStorage</code>等 API；但在 Web 开发中，我们使用的是<code>fetch/localStorage</code>等内置的函数或者对象</p>
</blockquote>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12c9ef95" alt></p>
<p>小程序的 API 实现是个巨大的黑盒，我们仅仅知道如何使用它，使用它会得到什么结果，但对它内部的实现一无所知。</p>
<p>如何让 Web 端也能使用小程序框架中提供的这些功能？既然已经知道这个黑盒的入参出参情况，那我们自己打造一个黑盒就好了。</p>
<p>换句话说，我们依然通过运行时框架来实现这些 Web 端不存在的能力。</p>
<p>具体说来，我们同样需要分析小程序原生 API，最后通过 Web 技术实现。有兴趣可以在 Git 仓库中看到这些原生 API 的实现。下面以<code>wx.setStorage</code>为例进行简单解析。</p>
<p><code>wx.setStorage</code>是一个异步接口，可以把<code>key: value</code>数据存储在本地缓存。很容易联想到，在 Web 开发中也有类似的数据存储概念，这就是<code>localStorage</code>。到这里，我们的目标已经十分明确：我们需要借助<code>于localStorage</code>，实现一个与<code>wx.setStorage</code>相同的 API。…</p>
<blockquote>
<p>而在 Web 中，如果我们需要往本地存储写入数据，使用的 API 是<code>localStorage.setItem(key, value)</code>。我们很容易就可以构思出这个函数的雏形</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* 示例代码 */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setStorage</span>(<span class="params">&#123; key, value &#125;</span>) </span>&#123;</span><br><span class="line">  localStorage.setItem(key, value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们顺手做点优化，把基于异步回调的 API 都给做了一层 Promise 包装，这可以让代码的流程处理更加方便。所以这段代码看起来会像下面这样：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* 示例代码 */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setStorage</span>(<span class="params">&#123; key, value &#125;</span>) </span>&#123;</span><br><span class="line">  localStorage.setItem(key, value);</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(&#123; <span class="attr">errMsg</span>: <span class="string">'setStorage:ok'</span> &#125;);</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p>看起来很完美，但开发的道路不会如此平坦。我们还需要处理其余的入参：success、fail和complete。success回调会在操作成功完成时调用，fail会在操作失败的时候执行，complete则无论如何都会执行。setStorage函数只会在key值是String类型时有正确的行为，所以我们为这个函数添加了一个简单的类型判断，并在异常情况下执行fail回调。经过这轮变动，这段代码看起来会像下面这样…</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* 示例代码 */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">setStorage</span>(<span class="params">&#123; key, value, success, fail, complete &#125;</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> res = &#123; <span class="attr">errMsg</span>: <span class="string">'setStorage:ok'</span> &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> key === <span class="string">'string'</span>) &#123;</span><br><span class="line">    localStorage.setItem(key, value);</span><br><span class="line">    success &amp;&amp; success(res);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    fail &amp;&amp; fail(res);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.reject(res);</span><br><span class="line">  &#125;</span><br><span class="line">  complete &amp;&amp; complete(res);</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(&#123; <span class="attr">errMsg</span>: <span class="string">'setStorage:ok'</span> &#125;);</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<p>把这个 API 实现挂载到Taro模块之后，我们就可以通过<code>Taro.setStorage</code>来调用这个 API 了。</p>
<p>当然，也有一些 API 是 Web 端无论如何无法实现的，比如<code>wx.login</code>，又或者<code>wx.scanCode</code>。我们维护了一个 API 实现情况的列表，在实际的多端项目开发中应该尽可能避免使用它们…</p>
<h3 id="8-3-路由"><a href="#8-3-路由" class="headerlink" title="8.3 路由"></a>8.3 路由</h3><blockquote>
<p>作为小程序的一大能力，小程序框架中以栈的形式维护了当前所有的页面，由框架统一管理。用户只需要调用<code>wx.navigateTo</code>,<code>wx.navigateBack</code>,<code>wx.redirectTo</code>等官方 API，就可以实现页面的跳转、回退、重定向，而不需要关心页面栈的细节。但是作为多端项目，当我们…</p>
</blockquote>
<p>小程序的路由比较轻量。使用时，我们先通过<code>app.json</code>为小程序配置页面列表：</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"pages"</span>: [</span><br><span class="line">    <span class="string">"pages/index/index"</span>,</span><br><span class="line">    <span class="string">"pages/logs/logs"</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>在运行时，小程序内维护了一个页面栈，始终展示栈顶的页面（<code>Page</code>对象）。当用户进行跳转、后退等操作时，相应的会使页面栈进行入栈、出栈等操作</p>
</blockquote>
<p>同时，在页面栈发生路由变化时，还会触发相应页面的生命周期</p>
<p><strong>对于 Web 端单页应用路由，我们则以react-router为例进行说明</strong></p>
<ul>
<li>首先，<code>react-router</code>开始通过<code>history</code>工具监听页面路径的变化。</li>
<li>在页面路径发生变化时，<code>react-router</code>会根据新的<code>location</code>对象，触发 UI 层的更新。</li>
<li>至于 UI 层如何更新，则是取决于我们在Route组件中对页面路径和组件的绑定，甚至可以实现嵌套路由。</li>
<li>可以说，<code>react-router</code>的路由方案是组件级别的。</li>
<li>具体到<code>Taro</code>，为了保持跟小程序的行为一致，我们不需要细致到组件级别的路由方案，但需要为每次路由保存完整的页面栈。</li>
<li>实现形式上，我们参考<code>react-router</code>：监听页面路径变化，再触发<code>UI</code> 更新。这是<code>React</code>的精髓之一，单向数据流…</li>
</ul>
<p><img src="https://user-gold-cdn.xitu.io/2018/10/8/166515ae12fc3d2c" alt></p>
<blockquote>
<p><code>@tarojs/router</code>包中包含了一个轻量的<code>history</code>实现。<code>history</code>中维护了一个栈，用来记录页面历史的变化。对历史记录的监听，依赖两个事件：<code>hashchange</code>和<code>popstate</code>。</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* 示例代码 */</span></span><br><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">'hashchange'</span>, () =&gt; &#123;&#125;);</span><br><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">'popstate'</span>, () =&gt; &#123;&#125;)</span><br></pre></td></tr></table></figure>
<ul>
<li>对于使用 <code>Hash</code>模式的页面路由，每次页面跳转都会依次触发<code>popstate</code>和<code>hashchange</code>事件。由于在<code>popstate</code>的回调中可以取到当前页面的 <code>state</code>，我们选择它作为主要跳转逻辑的容器。</li>
<li>作为 UI 层，<code>@tarojs/router</code>包提供了一个<code>Router</code>组件，维护页面栈。与小程序类似，用户不需要手动调用<code>Router</code>组件，而是由Taro自动处理。</li>
<li>对于历史栈来说，无非就是三种操作：<code>push</code>, <code>pop</code>，还有<code>replace</code>。在历史栈变动时触发<code>Router</code>的回调，就可以让<code>Router</code>也同步变化。这就是Taro中路由的基本原理…</li>
</ul>
<h3 id="8-4-Redux-处理"><a href="#8-4-Redux-处理" class="headerlink" title="8.4 Redux 处理"></a>8.4 Redux 处理</h3><ul>
<li>每当提到React的数据流，我们就不得不提到Redux。通过合并Reducer，Redux可以让大型应用中的数据流更加规则、可预测。</li>
<li>我们在Taro中加入了Redux的支持，通过导入<code>@tarojs/redux</code>，即可在小程序端使用Redux的功能。</li>
<li>对于 Web 端，我们尝试直接使用<code>nerv-redux</code>包提供支持，但这会带来一些问题…</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> Nerv <span class="keyword">from</span> <span class="string">'nervjs'</span></span><br><span class="line"><span class="keyword">import</span> &#123; connect &#125; <span class="keyword">from</span> <span class="string">'nerv-redux'</span></span><br><span class="line"></span><br><span class="line">@connect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Index</span> <span class="keyword">extends</span> <span class="title">Nerv</span>.<span class="title">Componnet</span> </span>&#123;</span><br><span class="line">  componentDidShow() &#123; <span class="built_in">console</span>.log(<span class="string">'didShow'</span>) &#125;</span><br><span class="line">  componentDidMount() &#123; <span class="built_in">console</span>.log(<span class="string">'didMount'</span>) &#125;</span><br><span class="line">  render() &#123; <span class="keyword">return</span> <span class="string">''</span> &#125;</span><br><span class="line">&#125;...</span><br></pre></td></tr></table></figure>
<ul>
<li>回想一下前面讲的<code>componentDidShow</code>的实现：我们继承，并且改写 <code>componentDidMount</code>。</li>
<li>但是对于使用Redux的页面来说，我们继承的类，是经过<code>@connect</code>修饰过的一个高阶组件。</li>
<li>问题就出在这里：这个高阶组件的签名里并没有<code>componentDidShow</code>这一个函数。所以我们的 <code>componentDidMount</code> 内，理所当然是取不到<code>componentDidShow</code>的。</li>
<li>为了解决这个问题，我们对<code>react-redux</code>代码进行了一些小改装，这就是<code>@taro/redux-h5</code>的由来…</li>
</ul>
<h2 id="九、更多参考"><a href="#九、更多参考" class="headerlink" title="九、更多参考"></a>九、更多参考</h2><ul>
<li><a href="https://nervjs.github.io/taro/docs/GETTING-STARTED.html" target="_blank" rel="noopener">Taro官方文档</a></li>
</ul>

      </div>
    
  </div>

</article>

<button class="assist-btn2 circle" id="assist_btn2" title="点亮屏幕" style="left: 27px; top: 152px;">
  <i class="iconfont" style="display:inline-block;color:red;width:20px;height:20px;">&#xe61d;</i>
</button>
<button class="assist-btn1 circle" id="assist_btn1" title="关闭屏幕亮度" style="left: 27px; top: 152px;">
  <i class="iconfont toc-title" style="display:inline-block;color:red;width:20px;height:20px;">&#xe61d;</i>
</button>


<script src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>	

<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
<script>
  const btw = new BTWPlugin();
  btw.init({
    id: "container",
    blogId: "22699-1592137983091-414",
    name: "前端进阶之旅",
    qrcode: "https://poetries1.gitee.io/img-repo/2020/06/qrcode.jpg",
    keyword: "3a3b3c",
  });
</script>

<script type="text/javascript">

// white theme
var body = {color: "#555", background: "#000"};
var a_tag = {color: "#222"};
var header = { background: "#222"};
var logo_line_i = {background: "#222"};
// var post_code = {background: "#eee", color: "#222"};

function switch_theme() {
 $("body").css(body);
 $("a:not('.links-of-author-item a, .site-state-item a, .site-state-posts a, .feed-link a, .motion-element a, .post-tags a, .show-commit-cls a, #donate_board a')").css(a_tag);
 $(".header, .footer").css(header);
 $(".logo-line-before i, .logo-line-after i").css(logo_line_i);
 //$(".post code").css(post_code);
 $("#idhyt-surprise-ball #idhyt-surprise-ball-animation .drag").css(a_tag);
 $(".post-title-link, .posts-expand .post-meta, .post-comments-count, .disqus-comment-count, .post-category a, .post-nav-next a, .post-nav-item a").css(a_tag);
 
 // $("code").css({color: '#c5c8c6', background: '#1d1f21'});
 //$("#assist_btn1").hide(1500);
}

$(function () {
$("#assist_btn2").css("display","none");
 $("#assist_btn1").click(function() {
     switch_theme();
$("div#toc.toc-article").css({
 "background":"#eaeaea",
 "opacity":1
});
$(".toc-article ol").show();
$("#toc.toc-article .toc-title").css("color","#a98602");
$("#assist_btn1").css("display","none");
$("#assist_btn2").css("display","block");
 });
$("#assist_btn2").click(function() {
$("#assist_btn2").css("display","none");
$("#assist_btn1").css("display","block");
$("body").css("background","url(http://www.miaov.com/static/ie/images/news/bg.png)")
     $(".header, .footer").css("background","url(http://www.miaov.com/static/ie/images/news/bg.png)")
$(".toc-article ol").toggle(1000);
 });
});


//背景随机

var Y, O, E, L, B, C, T, z, N, S, A, I;
!function() {
var e = function() {
for (O.clearRect(0, 0, L, B), T = [{
x: 0,
y: .7 * B + C
}, {
x: 0,
y: .7 * B - C
}]; T[1].x < L + C;) t(T[0], T[1])
}, t = function(e, t) {
O.beginPath(), O.moveTo(e.x, e.y), O.lineTo(t.x, t.y);
var n = t.x + (2 * I() - .25) * C,
 r = a(t.y);
O.lineTo(n, r), O.closePath(), N -= S / -50, O.fillStyle = "#" + (127 * A(N) + 128 << 16 | 127 * A(N + S / 3) + 128 << 8 | 127 * A(N + S / 3 * 2) + 128).toString(16), O.fill(), T[0] = T[1], T[1] = {
 x: n,
 y: r
}
}, a = function n(e) {
var t = e + (2 * I() - 1.1) * C;
return t > B || t < 0 ? n(e) : t
};
Y = document.getElementById("evanyou"), O = Y.getContext("2d"), E = window.devicePixelRatio || 1, L = window.innerWidth, B = window.innerHeight, C = 90, z = Math, N = 0, S = 2 * z.PI, A = z.cos, I = z.random, Y.width = L * E, Y.height = B * E, O.scale(E, E), O.globalAlpha = .6, document.onclick = e, document.ontouchstart = e, e()
}()

   
$("#toc-eye").click(function(){
$("#toc.toc-article").toggle(1000);
});

</script>


   
  <div class="text-center donation">
    <div class="inner-donation">
      <span class="btn-donation">支持一下</span>
      <div class="donation-body">
        <div class="tip text-center">扫一扫，支持poetries</div>
        <ul>
        
          <li class="item">
            
              <span>微信扫一扫</span>
            
            <img src="/images/weixin.jpg" alt="">
          </li>
        
          <li class="item">
            
              <span>支付宝扫一扫</span>
            
            <img src="/images/zhifubao.jpg" alt="">
          </li>
        
        </ul>
      </div>
    </div>
  </div>


   
  <div class="box-prev-next clearfix">
    <a class="show pull-left" href="/2018/11/26/docker-pm2-deploy-node-proj/">
        <i class="icon icon-angle-left"></i>
    </a>
    <a class="show pull-right" href="/2018/11/27/nginx-module-summary/">
        <i class="icon icon-angle-right"></i>
    </a>
  </div>




</div>


  <a id="backTop" class="back-top">
    <i class="icon-angle-up"></i>
  </a>




  <div class="modal" id="modal">
  <span id="cover" class="cover hide"></span>
  <div id="modal-dialog" class="modal-dialog hide-dialog">
    <div class="modal-header">
      <span id="close" class="btn-close">关闭</span>
    </div>
    <hr>
    <div class="modal-body">
      <ul class="list-toolbox">
        
          <li class="item-toolbox">
            <a
              class="CIRCLE"
              href="/archives/"
              rel="noopener noreferrer"
              target="_self"
              >
              博客
            </a>
          </li>
        
          <li class="item-toolbox">
            <a
              class="CIRCLE"
              href="/categories/"
              rel="noopener noreferrer"
              target="_self"
              >
              分类
            </a>
          </li>
        
          <li class="item-toolbox">
            <a
              class="CIRCLE"
              href="/tags/"
              rel="noopener noreferrer"
              target="_self"
              >
              标签
            </a>
          </li>
        
          <li class="item-toolbox">
            <a
              class="CIRCLE"
              href="/search/"
              rel="noopener noreferrer"
              target="_self"
              >
              搜索
            </a>
          </li>
        
          <li class="item-toolbox">
            <a
              class="CIRCLE"
              href="/link/"
              rel="noopener noreferrer"
              target="_self"
              >
              友链
            </a>
          </li>
        
          <li class="item-toolbox">
            <a
              class="CIRCLE"
              href="/about/"
              rel="noopener noreferrer"
              target="_self"
              >
              关于
            </a>
          </li>
        
      </ul>

    </div>
  </div>
</div>



  
      <div class="fexo-comments comments-post">
    

    

    
    

    

    
    

    

<!-- Gitalk评论插件通用代码 -->
<div id="gitalk-container"></div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
<script>
const gitalk = new Gitalk({
  clientID: '5567a2c4abb858009d96',
  clientSecret: 'b9039ec056cf5c2346b3cdb63308a28c163f91e5',
  repo: 'poetries.github.io',
  owner: 'poetries',
  // 在这里设置一下截取前50个字符串, 这是因为 github 对 label 的长度有了要求, 如果超过
  // 50个字符串则会报错.
  // id: location.pathname.split('/').pop().substring(0, 49),
  id: location.pathname,
  admin: ['poetries'],
  // facebook-like distraction free mode
  distractionFreeMode: false
})
gitalk.render('gitalk-container')
</script>
<!-- Gitalk代码结束 -->



  </div>

  

  <script type="text/javascript">
  function loadScript(url, callback) {
    var script = document.createElement('script')
    script.type = 'text/javascript';

    if (script.readyState) { //IE
      script.onreadystatechange = function() {
        if (script.readyState == 'loaded' ||
          script.readyState == 'complete') {
          script.onreadystatechange = null;
          callback();
        }
      };
    } else { //Others
      script.onload = function() {
        callback();
      };
    }

    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script);
  }

  window.onload = function() {
    loadScript('/js/bundle.js?235683', function() {
      // load success
    });
  }
</script>


  <!-- 页面点击小红心 -->
  <script type="text/javascript" src="/js/clicklove.js"></script>
 
  
</body>
</html>
